From cf835164e555166072cdf2ba54b2056ddb8d71ff Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 16 Sep 2014 21:12:54 +0200 Subject: [PATCH 001/314] added content to the controller part --- 6-controllers.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/6-controllers.md b/6-controllers.md index 8bcd605..a9b534d 100644 --- a/6-controllers.md +++ b/6-controllers.md @@ -4,4 +4,52 @@ When I talk about a controller in this tutorial then I am just referring to a class that has handler methods. I am not talking about [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html) controllers. MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). +Create a new folder inside the `src/` folder with the name `HelloWorld`. This will be where all your hello world related code will end up in. In there, create `HelloWorldController.php`. + +``` +$method($vars); + break; +``` + +So instead of just calling a handler method you are now instantiating the controller object and then calling the method on it. + +Now if you visit `http://localhost:8000/hello-world` everything should work. If not, go back and debug. And of course don't forget to commit your changes. + to be continued... From d54233080f86c0859313164b372ccef776f8fe84 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 16 Sep 2014 21:14:29 +0200 Subject: [PATCH 002/314] prepared DI part and navigation --- 6-controllers.md | 4 ++-- 7-dependency-injection.md | 5 +++++ README.md | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 7-dependency-injection.md diff --git a/6-controllers.md b/6-controllers.md index a9b534d..1c0cb60 100644 --- a/6-controllers.md +++ b/6-controllers.md @@ -1,4 +1,4 @@ -[<< previous](5-router.md) +[<< previous](5-router.md) | [next >>](7-dependency-injection.md) ### Controllers @@ -52,4 +52,4 @@ So instead of just calling a handler method you are now instantiating the contro Now if you visit `http://localhost:8000/hello-world` everything should work. If not, go back and debug. And of course don't forget to commit your changes. -to be continued... +[<< previous](5-router.md) | [next >>](7-dependency-injection.md) diff --git a/7-dependency-injection.md b/7-dependency-injection.md new file mode 100644 index 0000000..530724d --- /dev/null +++ b/7-dependency-injection.md @@ -0,0 +1,5 @@ +[<< previous](5-router.md) | [next >>](7-dependency-injection.md) + +### Dependency Injection + +to be continued... \ No newline at end of file diff --git a/README.md b/README.md index c59e4ef..ee3483f 100644 --- a/README.md +++ b/README.md @@ -20,4 +20,4 @@ So let's get started right away with the [first part](1-front-controller.md). 4. [HTTP](4-http.md) 5. [Router](5-router.md) 6. [Controllers](6-controllers.md) -7. Dependency Injector +7. [Dependency Injection](7-dependency-injection.md) From 0427da3caaa3671fc6ea95879f3855a782d2f4d5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 16 Sep 2014 21:16:49 +0200 Subject: [PATCH 003/314] fixed navigation --- 7-dependency-injection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-dependency-injection.md b/7-dependency-injection.md index 530724d..41ad002 100644 --- a/7-dependency-injection.md +++ b/7-dependency-injection.md @@ -1,4 +1,4 @@ -[<< previous](5-router.md) | [next >>](7-dependency-injection.md) +[<< previous](6-controllers.md) ### Dependency Injection From ae6c24da5d3de884ceeafa4edb241f7d4db487e0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 16 Sep 2014 21:48:41 +0200 Subject: [PATCH 004/314] finished inversion of control part --- 6-controllers.md | 4 +-- 7-dependency-injection.md | 5 ---- 7-inversion-of-control.md | 51 +++++++++++++++++++++++++++++++++++++++ 9-dependency-injector.md | 5 ++++ README.md | 3 ++- 5 files changed, 60 insertions(+), 8 deletions(-) delete mode 100644 7-dependency-injection.md create mode 100644 7-inversion-of-control.md create mode 100644 9-dependency-injector.md diff --git a/6-controllers.md b/6-controllers.md index 1c0cb60..ca18500 100644 --- a/6-controllers.md +++ b/6-controllers.md @@ -1,4 +1,4 @@ -[<< previous](5-router.md) | [next >>](7-dependency-injection.md) +[<< previous](5-router.md) | [next >>](7-inversion-of-control.md) ### Controllers @@ -52,4 +52,4 @@ So instead of just calling a handler method you are now instantiating the contro Now if you visit `http://localhost:8000/hello-world` everything should work. If not, go back and debug. And of course don't forget to commit your changes. -[<< previous](5-router.md) | [next >>](7-dependency-injection.md) +[<< previous](5-router.md) | [next >>](7-inversion-of-control.md) \ No newline at end of file diff --git a/7-dependency-injection.md b/7-dependency-injection.md deleted file mode 100644 index 41ad002..0000000 --- a/7-dependency-injection.md +++ /dev/null @@ -1,5 +0,0 @@ -[<< previous](6-controllers.md) - -### Dependency Injection - -to be continued... \ No newline at end of file diff --git a/7-inversion-of-control.md b/7-inversion-of-control.md new file mode 100644 index 0000000..1146007 --- /dev/null +++ b/7-inversion-of-control.md @@ -0,0 +1,51 @@ +[<< previous](6-controllers.md) | [next >>](8-dependency-injector.md) + +### Inversion of Control + +In the last part you have set up a controller and were able to generate output with `echo`. But let's not forget that you have a nice object oriented HTTP abstraction available. But you still need to make it accessible inside the controller class. + +The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). + +If it sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is implemented things will make more sense. + +Change your `HelloWorldController` to the following: + +``` +response = $response; + } + + public function hello() + { + $this->response->setContent('Hello World'); + } +} +``` + +Please note that you are [importing](http://php.net/manual/en/language.namespaces.importing.php) `Http\Response` at the top of the file. This means that whenever you use `Response` inside this file, it will resolve to the fully qualified name. + +In the contructor you are now explicitly asking for a `Http\Response`. In this case, `Http\Response` is an interface. So any class that implements the interface can be injected. See [type hinting](http://php.net/manual/en/language.oop5.typehinting.php) and [interfaces](php.net/manual/en/language.oop5.interfaces.php) for reference. + +Now the code will result in an error because you are not actually injecting anything. So let's fix that in your `Bootstrap.php` where you dispatch when a route was found: + +``` +$class = new $className($response); +$class->$method($vars); +``` + +The `Http\HttpResponse` object implements the `Http\Response` interface, so it fulfills the contract and can be used. + +Now everything should work again. But if you follow this example, all your controllers will have the same objects injected. This is of course not good, so let's fix that in the next part. + +[<< previous](6-controllers.md) | [next >>](8-dependency-injector.md) \ No newline at end of file diff --git a/9-dependency-injector.md b/9-dependency-injector.md new file mode 100644 index 0000000..36f080d --- /dev/null +++ b/9-dependency-injector.md @@ -0,0 +1,5 @@ +[<< previous](7-inversion-of-control.md) + +### Inversion of Control + +to be continued... \ No newline at end of file diff --git a/README.md b/README.md index ee3483f..d805359 100644 --- a/README.md +++ b/README.md @@ -20,4 +20,5 @@ So let's get started right away with the [first part](1-front-controller.md). 4. [HTTP](4-http.md) 5. [Router](5-router.md) 6. [Controllers](6-controllers.md) -7. [Dependency Injection](7-dependency-injection.md) +7. [Inversion of Control](7-inversion-of-control.md) +8. [Dependency Injector](8-dependency-injector.md) From a847391433ed64e48d83d74b422b896fc4858ea4 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 16 Sep 2014 21:51:20 +0200 Subject: [PATCH 005/314] fixed the filename --- 9-dependency-injector.md => 8-dependency-injector.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename 9-dependency-injector.md => 8-dependency-injector.md (100%) diff --git a/9-dependency-injector.md b/8-dependency-injector.md similarity index 100% rename from 9-dependency-injector.md rename to 8-dependency-injector.md From 768e2ec17e4fa974f2bdeaf1b20e6834d9537ad1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 16 Sep 2014 21:52:08 +0200 Subject: [PATCH 006/314] fixed title --- 8-dependency-injector.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 36f080d..75f253a 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -1,5 +1,5 @@ [<< previous](7-inversion-of-control.md) -### Inversion of Control +### Dependency Injector to be continued... \ No newline at end of file From a710473db5b94d90f9c2a90d51621f70450f317f Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 16 Sep 2014 21:53:11 +0200 Subject: [PATCH 007/314] fixed link --- 7-inversion-of-control.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-inversion-of-control.md b/7-inversion-of-control.md index 1146007..b836e22 100644 --- a/7-inversion-of-control.md +++ b/7-inversion-of-control.md @@ -35,7 +35,7 @@ class HelloWorldController Please note that you are [importing](http://php.net/manual/en/language.namespaces.importing.php) `Http\Response` at the top of the file. This means that whenever you use `Response` inside this file, it will resolve to the fully qualified name. -In the contructor you are now explicitly asking for a `Http\Response`. In this case, `Http\Response` is an interface. So any class that implements the interface can be injected. See [type hinting](http://php.net/manual/en/language.oop5.typehinting.php) and [interfaces](php.net/manual/en/language.oop5.interfaces.php) for reference. +In the contructor you are now explicitly asking for a `Http\Response`. In this case, `Http\Response` is an interface. So any class that implements the interface can be injected. See [type hinting](http://php.net/manual/en/language.oop5.typehinting.php) and [interfaces](http://php.net/manual/en/language.oop5.interfaces.php) for reference. Now the code will result in an error because you are not actually injecting anything. So let's fix that in your `Bootstrap.php` where you dispatch when a route was found: From 070a8b0563db761dded1f6c2f3eabcdb0e014b71 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 16 Sep 2014 21:53:59 +0200 Subject: [PATCH 008/314] removed partial sentence --- 3-error-handler.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/3-error-handler.md b/3-error-handler.md index f3b887d..03c305c 100644 --- a/3-error-handler.md +++ b/3-error-handler.md @@ -23,8 +23,6 @@ Now run `composer update` in your console and it will be installed. But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you only have to add a `require '../vendor/autoload.php';` to your `Bootstrap.php`. -Now before you start adding the error handler code to the - **Important:** Never show any errors in your production environment. A stack trace or even just a simple error message can help someone to gain access to your system. Always show a user friendly error page instead and send an email to yourself, write to a log or something similar. So only you can see the errors in the production environment. For development that does not make sense though -- you want a nice error page. The solution is to have an environment switch in your code. For now you can just set it to `development`. From 1919bffec9ec02bbcbe05d8ead3e5fcf50bc60c5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 17 Sep 2014 21:22:11 +0200 Subject: [PATCH 009/314] started di part --- 8-dependency-injector.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 75f253a..f942025 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -2,4 +2,10 @@ ### Dependency Injector -to be continued... \ No newline at end of file +A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when the class is instantiated. + +My favorite injector is [Auryn](https://github.com/rdlowrey/Auryn), so install `rdlowrey/auryn` with composer or use one of the alternatives below: + +[Pimple](http://pimple.sensiolabs.org/), [Orno DI](https://github.com/orno/di), [PHP-DI](https://github.com/mnapoli/PHP-DI) + +to be continued... From 79cd1b51b9cee99fa213412efca9d8b458aaea28 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 17 Sep 2014 21:46:06 +0200 Subject: [PATCH 010/314] added content to di part --- 8-dependency-injector.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index f942025..2ee20e7 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -8,4 +8,40 @@ My favorite injector is [Auryn](https://github.com/rdlowrey/Auryn), so install ` [Pimple](http://pimple.sensiolabs.org/), [Orno DI](https://github.com/orno/di), [PHP-DI](https://github.com/mnapoli/PHP-DI) +Create a new file called `Dependencies.php` in your `src/` folder. In there add the following content: + +``` +share('Http\CookieBuilder'); +$injector->delegate('Http\CookieBuilder', function($environment){ + $cookieBuilder = new \Http\CookieBuilder; + $cookieBuilder->setDefaultSecure($environment === 'production'); + return $cookieBuilder; +}); + +$injector->alias('Http\Response', 'Http\HttpResponse'); +$injector->share('Http\HttpRequest'); +$injector->define('Http\HttpRequest', [ + ':get' => $_GET, + ':post' => $_POST, + ':cookies' => $_COOKIE, + ':files' => $_FILES, + ':server' => $_SERVER, +]); + +$injector->alias('Http\Request', 'Http\HttpRequest'); +$injector->share('Http\HttpResponse'); + +return $injector; +``` + +Make sure you understand what `alias`, `share` and `define` are doing before you continue. You can read about them in the [Auryn documentation](https://github.com/rdlowrey/Auryn). + +You are sharing the HTTP objects because there would not be much point in adding content to one object and then returning another one. So by sharing it you always get the same instance. + +The alias allows you to type hint the interface instead of the class name. This makes it easy to switch the implementation without having to go back and edit all your classes that use the old implementation. + to be continued... From 479b5d3ff838bb4882516ca4d09931099ecf4fac Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 17 Sep 2014 22:05:09 +0200 Subject: [PATCH 011/314] expanded di part --- 8-dependency-injector.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 2ee20e7..8ef7e8a 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -10,7 +10,7 @@ My favorite injector is [Auryn](https://github.com/rdlowrey/Auryn), so install ` Create a new file called `Dependencies.php` in your `src/` folder. In there add the following content: -``` +```php make('Http\HttpRequest'); +$response = $injector->make('Http\HttpResponse'); +``` + +The other part that has to be changed is the dispatching of the route. Before you had the following code: + +```php +$class = new $className($response); +$class->$method($vars); +``` + +Change that to the following: + +```php +$class = $injector->make($className); +$class->$method($vars); +``` + +Now all your controller constructor dependencies will be automatically resolved with Auryn. + to be continued... From 13a5d0fb0196eb91f065456ad648b6debca200ee Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 17 Sep 2014 22:06:16 +0200 Subject: [PATCH 012/314] added code formatting --- 6-controllers.md | 6 +++--- 7-inversion-of-control.md | 4 ++-- 8-dependency-injector.md | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/6-controllers.md b/6-controllers.md index ca18500..99a41a1 100644 --- a/6-controllers.md +++ b/6-controllers.md @@ -6,7 +6,7 @@ When I talk about a controller in this tutorial then I am just referring to a cl Create a new folder inside the `src/` folder with the name `HelloWorld`. This will be where all your hello world related code will end up in. In there, create `HelloWorldController.php`. -``` +```php $method($vars); ``` diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 8ef7e8a..2290598 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -5,7 +5,6 @@ A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when the class is instantiated. My favorite injector is [Auryn](https://github.com/rdlowrey/Auryn), so install `rdlowrey/auryn` with composer or use one of the alternatives below: - [Pimple](http://pimple.sensiolabs.org/), [Orno DI](https://github.com/orno/di), [PHP-DI](https://github.com/mnapoli/PHP-DI) Create a new file called `Dependencies.php` in your `src/` folder. In there add the following content: From 38f688693321ac5a93758dcf8e87ea45297a657b Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 17 Sep 2014 22:07:26 +0200 Subject: [PATCH 013/314] added code formatting --- 1-front-controller.md | 4 ++-- 2-composer.md | 4 ++-- 3-error-handler.md | 4 ++-- 4-http.md | 10 +++++----- 5-router.md | 6 +++--- 8-dependency-injector.md | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/1-front-controller.md b/1-front-controller.md index 413924c..4062204 100644 --- a/1-front-controller.md +++ b/1-front-controller.md @@ -16,7 +16,7 @@ So instead of doing that, create a folder in your project folder called `public` Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: -``` +```php =5.5.0", "filp/whoops": ">=1.1.2" @@ -29,7 +29,7 @@ For development that does not make sense though -- you want a nice error page. T Then after the error handler registration, throw an `Exception` to test if everything is working correctly. Your `Bootstrap.php` should now look similar to this: -``` +```php =5.5.0", "filp/whoops": ">=1.1.2", @@ -24,7 +24,7 @@ Again, edit the `composer.json` to add the new component and then run `composer Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): -``` +```php $request = new \Http\HttpRequest($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER); $response = new \Http\HttpResponse; ``` @@ -33,7 +33,7 @@ This sets up the `Request` and `Response` objects that you can use in your other To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: -``` +```php foreach ($response->getHeaders() as $header) { header($header); } @@ -45,14 +45,14 @@ This will send the response data to the browser. If you don't do this, nothing h Right now it is just sending an empty response back to the browser with the status code `200`; to change that, add the following code between the code snippets from above: -``` +```php $content = '

Hello World

'; $response->setContent($content); ``` If you want to try a 404 error, use the following code: -``` +```php $response->setContent('404 - Page not found'); $response->setStatusCode(404); ``` diff --git a/5-router.md b/5-router.md index f59be70..b559a51 100644 --- a/5-router.md +++ b/5-router.md @@ -14,7 +14,7 @@ By now you know how to install Composer packages, so I will leave that to you. Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last part. -``` +```php $dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { $r->addRoute('GET', '/hello-world', function () { echo 'Hello World'; @@ -48,7 +48,7 @@ This setup might work for really small applications, but once you start adding a Create a `Routes.php` file in the `src/` folder. It should look like this: -``` +```php Date: Wed, 17 Sep 2014 22:14:25 +0200 Subject: [PATCH 014/314] switched from uri to path --- 4-http.md | 2 +- 5-router.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/4-http.md b/4-http.md index 8229b76..0b148aa 100644 --- a/4-http.md +++ b/4-http.md @@ -18,7 +18,7 @@ Again, edit the `composer.json` to add the new component and then run `composer "require": { "php": ">=5.5.0", "filp/whoops": ">=1.1.2", - "patricklouys/http": ">=1.0.2" + "patricklouys/http": ">=1.0.3" }, ``` diff --git a/5-router.md b/5-router.md index b559a51..764d318 100644 --- a/5-router.md +++ b/5-router.md @@ -24,7 +24,7 @@ $dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r }); }); -$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri()); +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getPath()); switch ($routeInfo[0]) { case \FastRoute\Dispatcher::NOT_FOUND: $response->setContent('404 - Page not found'); From 4759982c32477c0727d0dff09d0d36100b120761 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 17 Sep 2014 22:45:17 +0200 Subject: [PATCH 015/314] changed to correct version number --- 4-http.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/4-http.md b/4-http.md index 0b148aa..0cbb5fa 100644 --- a/4-http.md +++ b/4-http.md @@ -18,7 +18,7 @@ Again, edit the `composer.json` to add the new component and then run `composer "require": { "php": ">=5.5.0", "filp/whoops": ">=1.1.2", - "patricklouys/http": ">=1.0.3" + "patricklouys/http": ">=1.1.0" }, ``` From 2b12237f12809aac4722b5396a6683a48cfff4da Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 18 Sep 2014 21:53:50 +0200 Subject: [PATCH 016/314] finished DI part --- 8-dependency-injector.md | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 1cfef96..110332a 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -68,4 +68,38 @@ $class->$method($vars); Now all your controller constructor dependencies will be automatically resolved with Auryn. -to be continued... +Go back to your `HelloWorldController.php` and change it to the following: + +```php +request = $request; + $this->response = $response; + } + + public function hello() + { + $content = '

Hello World

'; + $content .= 'Hello ' . $this->request->getParameter('name', 'stranger'); + $this->response->setContent($content); + } +} +``` + +As you can see now the class has two dependencies. Try to access the page with a GET parameter like this `http://localhost:8000/hello-world?name=Arthur%20Dent`. + +Congratulations, you have now successfully laid the groundwork for your application. + +[<< previous](7-inversion-of-control.md) \ No newline at end of file From 31b293c49be873fa871ba9eb256eab238699ac2a Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 19 Sep 2014 15:50:19 +0200 Subject: [PATCH 017/314] changed di recommendation --- 8-dependency-injector.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 110332a..ef7e2d1 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -4,8 +4,7 @@ A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when the class is instantiated. -My favorite injector is [Auryn](https://github.com/rdlowrey/Auryn), so install `rdlowrey/auryn` with composer or use one of these alternatives: -[Pimple](http://pimple.sensiolabs.org/), [Orno DI](https://github.com/orno/di), [PHP-DI](https://github.com/mnapoli/PHP-DI) +There is only one injector that I can recommend: [Auryn](https://github.com/rdlowrey/Auryn). Sadly all the alternatives that I am aware of are using the [service locator antipattern](http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/). Create a new file called `Dependencies.php` in your `src/` folder. In there add the following content: From 26e04d9c0abd5acd9a25b1ff65c19cd9f6993095 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 20 Sep 2014 19:56:08 +0200 Subject: [PATCH 018/314] Changed former controller part --- 5-router.md | 4 +-- 6-controllers.md | 55 --------------------------------------- 7-inversion-of-control.md | 4 +-- README.md | 2 +- 4 files changed, 5 insertions(+), 60 deletions(-) delete mode 100644 6-controllers.md diff --git a/5-router.md b/5-router.md index 764d318..2bb5c59 100644 --- a/5-router.md +++ b/5-router.md @@ -1,4 +1,4 @@ -[<< previous](4-http.md) | [next >>](6-controllers.md) +[<< previous](4-http.md) | [next >>](6-dispatching-to-a-class.md) ### Router @@ -76,4 +76,4 @@ $dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); This is already an improvement, but now all the handler code is in the `Routers.php` file. This is not optimal, so let's fix that in the next part. -[<< previous](4-http.md) | [next >>](6-controllers.md) +[<< previous](4-http.md) | [next >>](6-dispatching-to-a-class.md) diff --git a/6-controllers.md b/6-controllers.md deleted file mode 100644 index 99a41a1..0000000 --- a/6-controllers.md +++ /dev/null @@ -1,55 +0,0 @@ -[<< previous](5-router.md) | [next >>](7-inversion-of-control.md) - -### Controllers - -When I talk about a controller in this tutorial then I am just referring to a class that has handler methods. I am not talking about [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html) controllers. MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). - -Create a new folder inside the `src/` folder with the name `HelloWorld`. This will be where all your hello world related code will end up in. In there, create `HelloWorldController.php`. - -```php -$method($vars); - break; -``` - -So instead of just calling a handler method you are now instantiating the controller object and then calling the method on it. - -Now if you visit `http://localhost:8000/hello-world` everything should work. If not, go back and debug. And of course don't forget to commit your changes. - -[<< previous](5-router.md) | [next >>](7-inversion-of-control.md) \ No newline at end of file diff --git a/7-inversion-of-control.md b/7-inversion-of-control.md index 2454758..7f255e7 100644 --- a/7-inversion-of-control.md +++ b/7-inversion-of-control.md @@ -1,4 +1,4 @@ -[<< previous](6-controllers.md) | [next >>](8-dependency-injector.md) +[<< previous](6-dispatching-to-a-class.md) | [next >>](8-dependency-injector.md) ### Inversion of Control @@ -48,4 +48,4 @@ The `Http\HttpResponse` object implements the `Http\Response` interface, so it f Now everything should work again. But if you follow this example, all your controllers will have the same objects injected. This is of course not good, so let's fix that in the next part. -[<< previous](6-controllers.md) | [next >>](8-dependency-injector.md) \ No newline at end of file +[<< previous](6-dispatching-to-a-class.md) | [next >>](8-dependency-injector.md) \ No newline at end of file diff --git a/README.md b/README.md index d805359..d921ed6 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,6 @@ So let's get started right away with the [first part](1-front-controller.md). 3. [Error Handler](3-error-handler.md) 4. [HTTP](4-http.md) 5. [Router](5-router.md) -6. [Controllers](6-controllers.md) +6. [Dispatching to a Class](6-dispatching-to-a-class.md) 7. [Inversion of Control](7-inversion-of-control.md) 8. [Dependency Injector](8-dependency-injector.md) From ece781990d952be64d9cf9796848ee294342dda2 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 20 Sep 2014 19:56:51 +0200 Subject: [PATCH 019/314] Changed former controller part --- 6-dispatching-to-a-class.md | 57 +++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 6-dispatching-to-a-class.md diff --git a/6-dispatching-to-a-class.md b/6-dispatching-to-a-class.md new file mode 100644 index 0000000..31f2fbe --- /dev/null +++ b/6-dispatching-to-a-class.md @@ -0,0 +1,57 @@ +[<< previous](5-router.md) | [next >>](7-inversion-of-control.md) + +### Dispatching to a Class + +In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). + +Instead of just calling everything a controller, let's give our names descriptive names that describe what the class actually does. In this case, we will just display content, so a fitting name would be `Presenter`. If the class does something else, we will name it accordingly. + +Create a new folder inside the `src/` folder with the name `HelloWorld`. This will be where all your hello world related code will end up in. In there, create `HelloWorldPresenter.php`. + +```php +$method($vars); + break; +``` + +So instead of just calling a method you are now instantiating an object and then calling the method on it. + +Now if you visit `http://localhost:8000/hello-world` everything should work. If not, go back and debug. And of course don't forget to commit your changes. + +[<< previous](5-router.md) | [next >>](7-inversion-of-control.md) \ No newline at end of file From 7e07790e7633eacffc8477656c3c41d34d88d5da Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 27 Sep 2014 16:58:34 +0200 Subject: [PATCH 020/314] refactored to match earlier changes in different part --- 8-dependency-injector.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index ef7e2d1..43a3657 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -67,7 +67,7 @@ $class->$method($vars); Now all your controller constructor dependencies will be automatically resolved with Auryn. -Go back to your `HelloWorldController.php` and change it to the following: +Go back to your `HelloWorldPresenter.php` and change it to the following: ```php Date: Sun, 5 Oct 2014 21:03:11 +0200 Subject: [PATCH 021/314] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d921ed6..e88b86a 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,4 @@ So let's get started right away with the [first part](1-front-controller.md). 6. [Dispatching to a Class](6-dispatching-to-a-class.md) 7. [Inversion of Control](7-inversion-of-control.md) 8. [Dependency Injector](8-dependency-injector.md) +9. tbd... From bfc7e12a45f7ec1a36821f903fc8a6d2a7e8b9c8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 6 Oct 2014 22:48:28 +0200 Subject: [PATCH 022/314] added next topic --- 8-dependency-injector.md | 4 ++-- 8-templating.md | 7 +++++++ README.md | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 8-templating.md diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 43a3657..1ea1e7d 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -1,4 +1,4 @@ -[<< previous](7-inversion-of-control.md) +[<< previous](7-inversion-of-control.md) | [next >>](9-templating.md ### Dependency Injector @@ -101,4 +101,4 @@ As you can see now the class has two dependencies. Try to access the page with a Congratulations, you have now successfully laid the groundwork for your application. -[<< previous](7-inversion-of-control.md) \ No newline at end of file +[<< previous](7-inversion-of-control.md) | [next >>](9-templating.md \ No newline at end of file diff --git a/8-templating.md b/8-templating.md new file mode 100644 index 0000000..e038b5a --- /dev/null +++ b/8-templating.md @@ -0,0 +1,7 @@ +[<< previous](8-dependency-injector.md) + +### Templating + +coming soon... + +[<< previous](8-dependency-injector.md) \ No newline at end of file diff --git a/README.md b/README.md index e88b86a..650f42d 100644 --- a/README.md +++ b/README.md @@ -22,4 +22,4 @@ So let's get started right away with the [first part](1-front-controller.md). 6. [Dispatching to a Class](6-dispatching-to-a-class.md) 7. [Inversion of Control](7-inversion-of-control.md) 8. [Dependency Injector](8-dependency-injector.md) -9. tbd... +9. [Templating](9-templating.md) From f54f2092aa7564e3d0711d276f38e87ffd62026b Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 7 Oct 2014 22:59:11 +0200 Subject: [PATCH 023/314] into templating --- 8-templating.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/8-templating.md b/8-templating.md index e038b5a..fcab06b 100644 --- a/8-templating.md +++ b/8-templating.md @@ -2,6 +2,8 @@ ### Templating -coming soon... +A template engine is not necessary with PHP because the language itself can take care of that. But it can make things like escaping values easier. They also make it easier to draw a clear line between your application logic and the template files which should only put your variables into the HTML code. + +to be continued... [<< previous](8-dependency-injector.md) \ No newline at end of file From 21c838d2c98141a253372b75ed07321a0ad9de42 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 7 Oct 2014 23:05:08 +0200 Subject: [PATCH 024/314] fixed filename --- 8-templating.md => 9-templating.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename 8-templating.md => 9-templating.md (100%) diff --git a/8-templating.md b/9-templating.md similarity index 100% rename from 8-templating.md rename to 9-templating.md From 97c249f01fd586d2b8af7d6cf77d518709f6acad Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 7 Oct 2014 23:05:55 +0200 Subject: [PATCH 025/314] fixed links --- 8-dependency-injector.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 1ea1e7d..8c3fac4 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -1,4 +1,4 @@ -[<< previous](7-inversion-of-control.md) | [next >>](9-templating.md +[<< previous](7-inversion-of-control.md) | [next >>](9-templating.md) ### Dependency Injector @@ -101,4 +101,4 @@ As you can see now the class has two dependencies. Try to access the page with a Congratulations, you have now successfully laid the groundwork for your application. -[<< previous](7-inversion-of-control.md) | [next >>](9-templating.md \ No newline at end of file +[<< previous](7-inversion-of-control.md) | [next >>](9-templating.md) \ No newline at end of file From 169b846419f57f328546b9dd9c39fc78a6410c03 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 8 Oct 2014 19:08:26 +0200 Subject: [PATCH 026/314] added to intro --- 9-templating.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/9-templating.md b/9-templating.md index fcab06b..6cb00ea 100644 --- a/9-templating.md +++ b/9-templating.md @@ -4,6 +4,12 @@ A template engine is not necessary with PHP because the language itself can take care of that. But it can make things like escaping values easier. They also make it easier to draw a clear line between your application logic and the template files which should only put your variables into the HTML code. +A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). + +For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). + +Other well known alternatives would be [Twig](http://twig.sensiolabs.org/) or [Smarty](http://www.smarty.net/), but they are both pretty bloated and offer too much functionality for just a template engine. + to be continued... [<< previous](8-dependency-injector.md) \ No newline at end of file From 8f42bb59adfb64a80ec27ee8a1dc91dbd4cd9460 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 9 Oct 2014 19:18:45 +0200 Subject: [PATCH 027/314] added missing sentence --- 8-dependency-injector.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 8c3fac4..7ad36f6 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -6,7 +6,7 @@ A dependency injector resolves the dependencies of your class and makes sure tha There is only one injector that I can recommend: [Auryn](https://github.com/rdlowrey/Auryn). Sadly all the alternatives that I am aware of are using the [service locator antipattern](http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/). -Create a new file called `Dependencies.php` in your `src/` folder. In there add the following content: +Install the Auryn package and then create a new file called `Dependencies.php` in your `src/` folder. In there add the following content: ```php Date: Fri, 10 Oct 2014 16:05:22 +0200 Subject: [PATCH 028/314] improved writing --- 7-inversion-of-control.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-inversion-of-control.md b/7-inversion-of-control.md index 7f255e7..8459237 100644 --- a/7-inversion-of-control.md +++ b/7-inversion-of-control.md @@ -2,7 +2,7 @@ ### Inversion of Control -In the last part you have set up a controller and were able to generate output with `echo`. But let's not forget that you have a nice object oriented HTTP abstraction available. But you still need to make it accessible inside the controller class. +In the last part you have set up a controller and generated output with `echo`. But let's not forget that you have a nice object oriented HTTP abstraction available. But right now it's not accessible inside your class. The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). From b4ecd157908e622f8057360c4da0735fe7dd70ec Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Sat, 11 Oct 2014 18:12:58 +0200 Subject: [PATCH 029/314] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 650f42d..422dd74 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ### Introduction -If you are really new to the language, this is not for you. This tutorial is aimed at people who have grasped the basics of PHP and know a little bit about object-oriented programming. +If you are new to the language, this tutorial is not for you. This tutorial is aimed at people who have grasped the basics of PHP and know a little bit about object-oriented programming. You should at least heard of [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29). If you are not familiar with it, now would be a good time to familiarize yourself with the principles before you start with the tutorial. From e2ca3a39efa97d4ae720a0d8c59ade8176df6b4b Mon Sep 17 00:00:00 2001 From: Chris Wright Date: Thu, 6 Nov 2014 11:30:52 +0000 Subject: [PATCH 030/314] Always use absolute paths for includes It's not safe to assume that the web server will always give you a sane cwd. --- 1-front-controller.md | 2 +- 3-error-handler.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/1-front-controller.md b/1-front-controller.md index 4062204..9814ad2 100644 --- a/1-front-controller.md +++ b/1-front-controller.md @@ -19,7 +19,7 @@ Inside the `public` folder you can now create your `index.php`. Remember that yo ```php Date: Thu, 6 Nov 2014 20:47:22 +0100 Subject: [PATCH 031/314] fixed typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 422dd74..0a381a7 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ If you are new to the language, this tutorial is not for you. This tutorial is aimed at people who have grasped the basics of PHP and know a little bit about object-oriented programming. -You should at least heard of [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29). If you are not familiar with it, now would be a good time to familiarize yourself with the principles before you start with the tutorial. +You should have at least heard of [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29). If you are not familiar with it, now would be a good time to familiarize yourself with the principles before you start with the tutorial. I saw a lot of people coming into the Stack Overflow PHP chatroom and asking if framework X is any good. Most of the time the answer was that they should just use PHP and not a framework to build their application. But many are overwhelmed by this and don't know where to start. From e2f077f8d9633ff531d4ce1785a21f56cb028477 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 6 Nov 2014 20:47:42 +0100 Subject: [PATCH 032/314] added link to alternative opinion --- 9-templating.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-templating.md b/9-templating.md index 6cb00ea..2917f06 100644 --- a/9-templating.md +++ b/9-templating.md @@ -4,7 +4,7 @@ A template engine is not necessary with PHP because the language itself can take care of that. But it can make things like escaping values easier. They also make it easier to draw a clear line between your application logic and the template files which should only put your variables into the HTML code. -A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). +A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). From adba44c3c0fece0ba27ec816f006d7c8c1419794 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 6 Nov 2014 22:34:31 +0100 Subject: [PATCH 033/314] removed faulty code --- 8-dependency-injector.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 7ad36f6..d48ee35 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -13,13 +13,6 @@ Install the Auryn package and then create a new file called `Dependencies.php` i $injector = new \Auryn\Provider; -$injector->share('Http\CookieBuilder'); -$injector->delegate('Http\CookieBuilder', function($environment){ - $cookieBuilder = new \Http\CookieBuilder; - $cookieBuilder->setDefaultSecure($environment === 'production'); - return $cookieBuilder; -}); - $injector->alias('Http\Response', 'Http\HttpResponse'); $injector->share('Http\HttpRequest'); $injector->define('Http\HttpRequest', [ From a9ab29ea930ef499b921f46ecd36df0d595e8e18 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 6 Nov 2014 23:17:13 +0100 Subject: [PATCH 034/314] intro adapter --- 9-templating.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/9-templating.md b/9-templating.md index 2917f06..20a52ed 100644 --- a/9-templating.md +++ b/9-templating.md @@ -6,10 +6,35 @@ A template engine is not necessary with PHP because the language itself can take A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. -For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). +For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install that package before you continue. Other well known alternatives would be [Twig](http://twig.sensiolabs.org/) or [Smarty](http://www.smarty.net/), but they are both pretty bloated and offer too much functionality for just a template engine. -to be continued... +Now please go and have a look at the source code of the [engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class does not implement an interface. + +You could just type hint against the concrete class. But the problem with this approach is that you create tight coupling. + +In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want to add functionality to the engine. You can't do that without going back and changing all your code that is tightly coupled. + +What we want is loose coupling. We will type hint against an interface that implements an interface. So if you need another implementation, you just implement that interface in your new class and inject the new class instead. + +Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). This sounds a lot more complicated than it is, so just follow along. + +First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. A class can extend multiple interfaces if necessary. + +So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a new folder in your `src/` folder with the name `Template` where you can put all the template related things. + +In there create a new interface `Renderable.php` that looks like this: + +```php + Date: Fri, 7 Nov 2014 00:17:34 +0100 Subject: [PATCH 035/314] finished templating chapter --- 9-templating.md | 101 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/9-templating.md b/9-templating.md index 20a52ed..4c05168 100644 --- a/9-templating.md +++ b/9-templating.md @@ -31,10 +31,109 @@ In there create a new interface `Renderable.php` that looks like this: namespace Example\Template; -interface Renderable +interface Engine { public function render($template, $data = []); } ``` +Now that this is sorted out, let's create the mustache adapter class. In the same folder, create the file `MustacheEngineAdapter.php` with the following content: + +```php +engine = $engine; + } + + public function render($template, $data = []) + { + return $this->engine->render($template, $data); + } +} +``` + +As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple and only fulfills the interface. + +Of course we also have to add a definition in our `Dependencies.php` file because otherwise the injector won't know which implementation he has to inject when you hint for the interface. Add this line: + +`$injector->alias('Example\Template\Engine', 'Example\Template\MustacheEngineAdapter');` + +Now in your `HelloWorldPresenter`, add the new dependency like this: + +```php +request = $request; + $this->response = $response; + $this->templateEngine = $templateEngine; + } + +... +``` + +As you can see I imported the engine with an alias. Without the full namespace it would be relatively unclear what a class does if it is just referenced by `Engine`. Also, another part of the application might also have a class with the name `Engine`. So to avoid that I give it a short and descriptive alias. + +We also have to rewrite the `hello` method. Please note that while we are just passing in a simple array, Mustache also gives you the option to pass in a view context object. We will go over this later, for now let's keep it as simple as possible. + +```php + public function hello() + { + $data = [ + 'name' => $this->request->getParameter('name', 'stranger'), + ]; + $content = $this->templateEngine->render('Hello {{name}}', $data); + $this->response->setContent($content); + } +``` + +Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. But what we want is template files, so let's go back and change that. + +To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the `Dependencies.php` file and add the following code: + +```php +$injector->define('Mustache_Engine', [ + ':options' => [ + 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__DIR__).'/templates'), + ], +]); +``` + +In your project root folder, create a `templates` folder. In there, create a folder `HelloWorld` and in there a file `Hello.mustache`. The content of the file should look like this: + +``` +

Hello World

+Hello {{ name }} +``` + +Now you can go back to your `HelloWorldPresenter` and change the render line to `$content = $this->templateEngine->render('HelloWorld/Hello', $data);` + +Navigate to the hello page in your browser to make sure everything works. And as always, don't forget to commit your changes. + [<< previous](8-dependency-injector.md) \ No newline at end of file From 9710403de149320e7047c3a341ffcba7aeb54f2b Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 7 Nov 2014 09:37:17 +0100 Subject: [PATCH 036/314] missing spaces --- 9-templating.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-templating.md b/9-templating.md index 4c05168..29f143c 100644 --- a/9-templating.md +++ b/9-templating.md @@ -120,7 +120,7 @@ To make this change we need to pass an options array to the `Mustache_Engine` co ```php $injector->define('Mustache_Engine', [ ':options' => [ - 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__DIR__).'/templates'), + 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__DIR__) . '/templates'), ], ]); ``` From d51b44c6d99c8297acf713d517c7f56bfcec91dc Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 16 Nov 2014 20:04:42 +0100 Subject: [PATCH 037/314] this fixes #9 --- 7-inversion-of-control.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/7-inversion-of-control.md b/7-inversion-of-control.md index 8459237..ecfec07 100644 --- a/7-inversion-of-control.md +++ b/7-inversion-of-control.md @@ -2,13 +2,13 @@ ### Inversion of Control -In the last part you have set up a controller and generated output with `echo`. But let's not forget that you have a nice object oriented HTTP abstraction available. But right now it's not accessible inside your class. +In the last part you have set up a presenter class and generated output with `echo`. But let's not forget that you have a nice object oriented HTTP abstraction available. But right now it's not accessible inside your class. The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). If it sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is implemented things will make more sense. -Change your `HelloWorldController` to the following: +Change your `HelloWorldPresenter` to the following: ```php $method($vars); The `Http\HttpResponse` object implements the `Http\Response` interface, so it fulfills the contract and can be used. -Now everything should work again. But if you follow this example, all your controllers will have the same objects injected. This is of course not good, so let's fix that in the next part. +Now everything should work again. But if you follow this example, all your objects that are instantiated this way will have the same objects injected. This is of course not good, so let's fix that in the next part. [<< previous](6-dispatching-to-a-class.md) | [next >>](8-dependency-injector.md) \ No newline at end of file From 89007243d7c41019298c52cb381336d9ec5bb267 Mon Sep 17 00:00:00 2001 From: Hari K T Date: Thu, 27 Nov 2014 22:38:41 +0530 Subject: [PATCH 038/314] aura/web , not aura/http . --- 4-http.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/4-http.md b/4-http.md index 0cbb5fa..d1ce904 100644 --- a/4-http.md +++ b/4-http.md @@ -8,7 +8,7 @@ These are good if you just want to get a small script up and running without muc Once again, you don't have to reinvent the wheel and just install a package. I decided to write my own [HTTP component](https://github.com/PatrickLouys/http) because I did not like the existing components, but you don't have to do the same. -Some alternatives: [Symfony HttpFoundation](https://github.com/symfony/HttpFoundation), [Nette HTTP Component](https://github.com/nette/http), [Aura Http](https://github.com/auraphp/Aura.Http), [sabre/http](https://github.com/fruux/sabre-http) +Some alternatives: [Symfony HttpFoundation](https://github.com/symfony/HttpFoundation), [Nette HTTP Component](https://github.com/nette/http), [Aura Web](https://github.com/auraphp/Aura.Web), [sabre/http](https://github.com/fruux/sabre-http) In this tutorial I will use my own HTTP component, but of course you can use whichever package you like most. Just change the code accordingly. From 87b4cf3a76a06d1498a8ae9aeb9ee3e6fac83aab Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 30 Nov 2014 21:06:11 +0100 Subject: [PATCH 039/314] changed to controllers --- 6-dispatching-to-a-class.md | 21 ++++++++++----------- 7-inversion-of-control.md | 12 ++++++------ 8-dependency-injector.md | 10 +++++----- 9-templating.md | 14 +++++++------- 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/6-dispatching-to-a-class.md b/6-dispatching-to-a-class.md index 31f2fbe..52c96e9 100644 --- a/6-dispatching-to-a-class.md +++ b/6-dispatching-to-a-class.md @@ -2,20 +2,22 @@ ### Dispatching to a Class -In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). +In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) and the followup posts. -Instead of just calling everything a controller, let's give our names descriptive names that describe what the class actually does. In this case, we will just display content, so a fitting name would be `Presenter`. If the class does something else, we will name it accordingly. +So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). -Create a new folder inside the `src/` folder with the name `HelloWorld`. This will be where all your hello world related code will end up in. In there, create `HelloWorldPresenter.php`. +We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Controllers` because that will be familiar for the people coming from a framework background. You could also name them `Handlers`. + +Create a new folder inside the `src/` folder with the name `Controllers`.In this folder we will place all our controller classes. In there, create a `Homepage.php` file. ```php >](7-inversion-of-control.md) \ No newline at end of file diff --git a/7-inversion-of-control.md b/7-inversion-of-control.md index ecfec07..1192545 100644 --- a/7-inversion-of-control.md +++ b/7-inversion-of-control.md @@ -2,22 +2,22 @@ ### Inversion of Control -In the last part you have set up a presenter class and generated output with `echo`. But let's not forget that you have a nice object oriented HTTP abstraction available. But right now it's not accessible inside your class. +In the last part you have set up a controller class and generated output with `echo`. But let's not forget that you have a nice object oriented HTTP abstraction available. But right now it's not accessible inside your class. The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). If it sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is implemented things will make more sense. -Change your `HelloWorldPresenter` to the following: +Change your `Homepage` controller to the following: ```php response = $response; } - public function hello() + public function show() { $this->response->setContent('Hello World'); } } ``` -Please note that you are [importing](http://php.net/manual/en/language.namespaces.importing.php) `Http\Response` at the top of the file. This means that whenever you use `Response` inside this file, it will resolve to the fully qualified name. +Note that you are [importing](http://php.net/manual/en/language.namespaces.importing.php) `Http\Response` at the top of the file. This means that whenever you use `Response` inside this file, it will resolve to the fully qualified name. In the contructor you are now explicitly asking for a `Http\Response`. In this case, `Http\Response` is an interface. So any class that implements the interface can be injected. See [type hinting](http://php.net/manual/en/language.oop5.typehinting.php) and [interfaces](http://php.net/manual/en/language.oop5.interfaces.php) for reference. diff --git a/8-dependency-injector.md b/8-dependency-injector.md index d48ee35..f4939af 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -60,17 +60,17 @@ $class->$method($vars); Now all your controller constructor dependencies will be automatically resolved with Auryn. -Go back to your `HelloWorldPresenter.php` and change it to the following: +Go back to your `Homepage` controller and change it to the following: ```php response = $response; } - public function hello() + public function show() { $content = '

Hello World

'; $content .= 'Hello ' . $this->request->getParameter('name', 'stranger'); @@ -90,7 +90,7 @@ class HelloWorldPresenter } ``` -As you can see now the class has two dependencies. Try to access the page with a GET parameter like this `http://localhost:8000/hello-world?name=Arthur%20Dent`. +As you can see now the class has two dependencies. Try to access the page with a GET parameter like this `http://localhost:8000/?name=Arthur%20Dent`. Congratulations, you have now successfully laid the groundwork for your application. diff --git a/9-templating.md b/9-templating.md index 29f143c..50ccb93 100644 --- a/9-templating.md +++ b/9-templating.md @@ -68,18 +68,18 @@ Of course we also have to add a definition in our `Dependencies.php` file becaus `$injector->alias('Example\Template\Engine', 'Example\Template\MustacheEngineAdapter');` -Now in your `HelloWorldPresenter`, add the new dependency like this: +Now in your `Homepage` controller, add the new dependency like this: ```php $this->request->getParameter('name', 'stranger'), @@ -125,14 +125,14 @@ $injector->define('Mustache_Engine', [ ]); ``` -In your project root folder, create a `templates` folder. In there, create a folder `HelloWorld` and in there a file `Hello.mustache`. The content of the file should look like this: +In your project root folder, create a `templates` folder. In there, create a file `Homepage.mustache`. The content of the file should look like this: ```

Hello World

Hello {{ name }} ``` -Now you can go back to your `HelloWorldPresenter` and change the render line to `$content = $this->templateEngine->render('HelloWorld/Hello', $data);` +Now you can go back to your `Homepage` controller and change the render line to `$content = $this->templateEngine->render('Homepage', $data);` Navigate to the hello page in your browser to make sure everything works. And as always, don't forget to commit your changes. From 87983c57e1032f17dde9d415e2a34a3a060bcf4b Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 30 Nov 2014 22:38:48 +0100 Subject: [PATCH 040/314] new chapter --- 10-dynamic-pages.md | 225 ++++++++++++++++++++++++++++++++++++++++++++ 9-templating.md | 8 +- 2 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 10-dynamic-pages.md diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md new file mode 100644 index 0000000..a416cec --- /dev/null +++ b/10-dynamic-pages.md @@ -0,0 +1,225 @@ +[<< previous](9-templating.md) + +### Dynamic Pages + +So far we only have a static page with not much functionality. Just having a hello world example is not very useful, so let's go beyond that and add some real functionality to our application. + +Our first feature will be dynamic pages generated from [markdown](http://en.wikipedia.org/wiki/Markdown) files. + +Create a `Page` controller with the following content: + +```php +pageFolder = $pageFolder; + } + + public function getContentBySlug($slug) + { + return 'I am a placeholder'; + } +} +``` + +As you can see we are requiring the page folder path as a constructor argument. This makes the class flexible and if we decide to move files or write unit tests for the class, we can easily change the location with the constructor argument. + +You could also put the page related things into it's own package and reuse it in different applications. Because we are not tightly coupling things, things are very flexible. + +Because PHP does not have the ability to type hint for scalar values (things like strings and integers), we have to manually check that `$pageFolder` is a string. If we don't do that, there might be a bug in the future that is hard to find if a wrong type is injected. By throwing an exception, this can be caught and debugged immediately. + +This will do for now. Let's create a template file for our pages with the name `Page.mustache` in the `templates` folder. For now just add `{{ content }}` in there. + +Add the following to your `Dependencies.php` file so that the application know which implementation to inject for our new interface. We also define the the `pageFolder` there. + +```php +$injector->define('Example\Page\FilePageReader', [ + ':pageFolder' => __DIR__ . '/../pages', +]); + +$injector->alias('Example\Page\PageReader', 'Example\Page\FilePageReader'); +$injector->share('Example\Page\FilePageReader'); +``` + + +Now go back to the `Page` controller and change the `show` method to the following: + +```php +public function show($params) +{ + $slug = $params['slug']; + $data['content'] = $this->pageReader->getContentBySlug($slug); + $html = $this->templateEngine->render('Page', $data); + $this->response->setContent($html); +} +``` + +To make this work, we will need to inject a `Response`, `TemplateEngine` and a `PageReader`. I will leave this to you as an exercise. Remember to `use` all the proper namespaces. Use the `Homepage` controller as a reference. + +Did you get everything to work? + +If not, this is how the beginning of your controller should look now: + +```php +response = $response; + $this->templateEngine = $templateEngine; + $this->pageReader = $pageReader; + } +... +``` + +So far so good, now let's make our `FilePageReader` actually do some work. + +Again, let's check first that the proper type was passed into the method: + +```php +public function getContentBySlug($slug) +{ + if (!is_string($slug)) { + throw new InvalidArgumentException('slug must be a string'); + } +} +``` + +We also need to be able to communicate that a page was not found. For this we can create a custom exception that we can catch later. In your `src/Page` folder, create a `InvalidPageException.php` file with this content: + +```php +pageFolder/$slug.md"; + +if(!file_exists($path)) { + throw new InvalidPageException($slug); +} + +return file_get_contents($path); +``` + +Now if you navigate to a page that does not exist, you should see an `InvalidPageException`. If a file exists, you should see the content. + +Of course showing the user an exception for an invalid URL does not make sense. So let's catch the exception and show a 404 error instead. + +Go to your `Page` controller and refactor the `show` method so that it looks like this: + +```php +public function show($params) +{ + $slug = $params['slug']; + + try { + $data['content'] = $this->pageReader->getContentBySlug($slug); + } catch (InvalidPageException $e) { + $this->response->setStatusCode(404); + return $this->response->setContent('404 - Page not found'); + } + + $html = $this->templateEngine->render('Page', $data); + $this->response->setContent($html); +} +``` + +Add this at the top of your file: +```php +use Example\Page\InvalidPageException; +``` + +It is important that you don't forget this step, otherwise it will try to catch the wrong exception (it's looking in the wrong namespace) and thus will never catch it. + +Try a few different URLs to check that everything is working as it should. If something is wrong, go back and debug it until it works. + +And as always, don't forget to commit your changes. +[<< previous](9-templating.md) \ No newline at end of file diff --git a/9-templating.md b/9-templating.md index 50ccb93..564e9c1 100644 --- a/9-templating.md +++ b/9-templating.md @@ -1,4 +1,4 @@ -[<< previous](8-dependency-injector.md) +[<< previous](8-dependency-injector.md) | [next >>](10-dynamic-pages.md) ### Templating @@ -108,8 +108,8 @@ We also have to rewrite the `show` method. Please note that while we are just pa $data = [ 'name' => $this->request->getParameter('name', 'stranger'), ]; - $content = $this->templateEngine->render('Hello {{name}}', $data); - $this->response->setContent($content); + $html = $this->templateEngine->render('Hello {{name}}', $data); + $this->response->setContent($html); } ``` @@ -136,4 +136,4 @@ Now you can go back to your `Homepage` controller and change the render line to Navigate to the hello page in your browser to make sure everything works. And as always, don't forget to commit your changes. -[<< previous](8-dependency-injector.md) \ No newline at end of file +[<< previous](8-dependency-injector.md) | [next >>](10-dynamic-pages.md) \ No newline at end of file From d64b6d9aa147be273264208d0babb97e2888da1c Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 30 Nov 2014 22:41:27 +0100 Subject: [PATCH 041/314] missing newline --- 10-dynamic-pages.md | 1 + 1 file changed, 1 insertion(+) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index a416cec..9d2b789 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -222,4 +222,5 @@ It is important that you don't forget this step, otherwise it will try to catch Try a few different URLs to check that everything is working as it should. If something is wrong, go back and debug it until it works. And as always, don't forget to commit your changes. + [<< previous](9-templating.md) \ No newline at end of file From 60e96fb592c4e644b95a015b21605c898973f075 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 30 Nov 2014 22:42:31 +0100 Subject: [PATCH 042/314] added link to new chapter --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0a381a7..03b3de6 100644 --- a/README.md +++ b/README.md @@ -23,3 +23,4 @@ So let's get started right away with the [first part](1-front-controller.md). 7. [Inversion of Control](7-inversion-of-control.md) 8. [Dependency Injector](8-dependency-injector.md) 9. [Templating](9-templating.md) +10. [Dynamic Pages](10-dynamic-pages.md) From 5facb772eaa8ff5ec1acf3e68dd90ace7f68ba2c Mon Sep 17 00:00:00 2001 From: Hassan Althaf Date: Thu, 4 Dec 2014 11:13:08 +0530 Subject: [PATCH 043/314] Update 9-templating.md Change file name 'Renderable' to 'Engine' because the interface is not found by Auryn Auto Loader as it finds classes/interfaces by their file names. --- 9-templating.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/9-templating.md b/9-templating.md index 564e9c1..373303f 100644 --- a/9-templating.md +++ b/9-templating.md @@ -24,7 +24,7 @@ First let's define the interface that we want. Remember the [interface segregati So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a new folder in your `src/` folder with the name `Template` where you can put all the template related things. -In there create a new interface `Renderable.php` that looks like this: +In there create a new interface `Engine.php` that looks like this: ```php >](10-dynamic-pages.md) \ No newline at end of file +[<< previous](8-dependency-injector.md) | [next >>](10-dynamic-pages.md) From 0bf3f2d3ce4a57454719ead421aa3284e88a6d17 Mon Sep 17 00:00:00 2001 From: HamZa Date: Mon, 8 Dec 2014 11:42:21 +0100 Subject: [PATCH 044/314] Changed $woops to $whoops --- 3-error-handler.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/3-error-handler.md b/3-error-handler.md index cb84972..760ca47 100644 --- a/3-error-handler.md +++ b/3-error-handler.md @@ -43,15 +43,15 @@ $environment = 'development'; /** * Register the error handler */ -$woops = new \Whoops\Run; +$whoops = new \Whoops\Run; if ($environment !== 'production') { - $woops->pushHandler(new \Whoops\Handler\PrettyPageHandler); + $whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler); } else { - $woops->pushHandler(function($e){ + $whoops->pushHandler(function($e){ echo 'Friendly error page and send an email to the developer'; }); } -$woops->register(); +$whoops->register(); throw new \Exception; From d1b04a16df5b25db2a43f002c2f8288c9573d703 Mon Sep 17 00:00:00 2001 From: HamZa Date: Mon, 8 Dec 2014 11:53:46 +0100 Subject: [PATCH 045/314] #18 changed $woops to $whoops --- 3-error-handler.md | 1 - 1 file changed, 1 deletion(-) diff --git a/3-error-handler.md b/3-error-handler.md index 760ca47..baa2358 100644 --- a/3-error-handler.md +++ b/3-error-handler.md @@ -60,4 +60,3 @@ throw new \Exception; You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until you get it working. Now would also be a good time for another commit. [<< previous](2-composer.md) | [next >>](4-http.md) - From c20b26c6846b0267129bbd080cf082dabdc507c1 Mon Sep 17 00:00:00 2001 From: Madara Date: Wed, 14 Jan 2015 15:32:21 +0200 Subject: [PATCH 046/314] Add leading zeros so that files list in correct order Fix all links to new file names --- ...nt-controller.md => 01-front-controller.md | 4 ++-- 2-composer.md => 02-composer.md | 4 ++-- 3-error-handler.md => 03-error-handler.md | 4 ++-- 4-http.md => 04-http.md | 4 ++-- 5-router.md => 05-router.md | 4 ++-- ...a-class.md => 06-dispatching-to-a-class.md | 4 ++-- ...f-control.md => 07-inversion-of-control.md | 4 ++-- ...y-injector.md => 08-dependency-injector.md | 4 ++-- 9-templating.md => 09-templating.md | 4 ++-- 10-dynamic-pages.md | 4 ++-- README.md | 20 +++++++++---------- 11 files changed, 30 insertions(+), 30 deletions(-) rename 1-front-controller.md => 01-front-controller.md (97%) rename 2-composer.md => 02-composer.md (93%) rename 3-error-handler.md => 03-error-handler.md (96%) rename 4-http.md => 04-http.md (96%) rename 5-router.md => 05-router.md (95%) rename 6-dispatching-to-a-class.md => 06-dispatching-to-a-class.md (94%) rename 7-inversion-of-control.md => 07-inversion-of-control.md (93%) rename 8-dependency-injector.md => 08-dependency-injector.md (95%) rename 9-templating.md => 09-templating.md (97%) diff --git a/1-front-controller.md b/01-front-controller.md similarity index 97% rename from 1-front-controller.md rename to 01-front-controller.md index 9814ad2..349c513 100644 --- a/1-front-controller.md +++ b/01-front-controller.md @@ -1,4 +1,4 @@ -[next >>](2-composer.md) +[next >>](02-composer.md) ### Front Controller @@ -40,4 +40,4 @@ If there is an error, go back and try to fix it. If you only see a blank page, c Now would be a good time to commit your progress. If you are not already using Git, set up a repository now. This is not a Git tutorial so I won't go over the details. But using version control should be a habit, even if it is just for a tutorial project like this. -[next >>](2-composer.md) +[next >>](02-composer.md) diff --git a/2-composer.md b/02-composer.md similarity index 93% rename from 2-composer.md rename to 02-composer.md index dfb9b00..fca4f45 100644 --- a/2-composer.md +++ b/02-composer.md @@ -1,4 +1,4 @@ -[<< previous](1-front-controller.md) | [next >>](3-error-handler.md) +[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) ### Composer @@ -51,4 +51,4 @@ This will exclude the included file and folder from your commits. For which now Now you have successfully created an empty playground which you can use to set up your project. -[<< previous](1-front-controller.md) | [next >>](3-error-handler.md) +[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) diff --git a/3-error-handler.md b/03-error-handler.md similarity index 96% rename from 3-error-handler.md rename to 03-error-handler.md index baa2358..401762c 100644 --- a/3-error-handler.md +++ b/03-error-handler.md @@ -1,4 +1,4 @@ -[<< previous](2-composer.md) | [next >>](4-http.md) +[<< previous](02-composer.md) | [next >>](04-http.md) ### Error Handler @@ -59,4 +59,4 @@ throw new \Exception; You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until you get it working. Now would also be a good time for another commit. -[<< previous](2-composer.md) | [next >>](4-http.md) +[<< previous](02-composer.md) | [next >>](04-http.md) diff --git a/4-http.md b/04-http.md similarity index 96% rename from 4-http.md rename to 04-http.md index d1ce904..660eece 100644 --- a/4-http.md +++ b/04-http.md @@ -1,4 +1,4 @@ -[<< previous](3-error-handler.md) | [next >>](5-router.md) +[<< previous](03-error-handler.md) | [next >>](05-router.md) ### HTTP @@ -61,4 +61,4 @@ Remember that the object is only storing data, so you if you set multiple status I will show you in later parts how to use the different features of the components. In the meantime, feel free to read the [documentation](https://github.com/PatrickLouys/http) or the source code if you want to find out how something works. -[<< previous](3-error-handler.md) | [next >>](5-router.md) +[<< previous](03-error-handler.md) | [next >>](05-router.md) diff --git a/5-router.md b/05-router.md similarity index 95% rename from 5-router.md rename to 05-router.md index 2bb5c59..a381107 100644 --- a/5-router.md +++ b/05-router.md @@ -1,4 +1,4 @@ -[<< previous](4-http.md) | [next >>](6-dispatching-to-a-class.md) +[<< previous](04-http.md) | [next >>](06-dispatching-to-a-class.md) ### Router @@ -76,4 +76,4 @@ $dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); This is already an improvement, but now all the handler code is in the `Routers.php` file. This is not optimal, so let's fix that in the next part. -[<< previous](4-http.md) | [next >>](6-dispatching-to-a-class.md) +[<< previous](04-http.md) | [next >>](06-dispatching-to-a-class.md) diff --git a/6-dispatching-to-a-class.md b/06-dispatching-to-a-class.md similarity index 94% rename from 6-dispatching-to-a-class.md rename to 06-dispatching-to-a-class.md index 52c96e9..3221f44 100644 --- a/6-dispatching-to-a-class.md +++ b/06-dispatching-to-a-class.md @@ -1,4 +1,4 @@ -[<< previous](5-router.md) | [next >>](7-inversion-of-control.md) +[<< previous](05-router.md) | [next >>](07-inversion-of-control.md) ### Dispatching to a Class @@ -53,4 +53,4 @@ So instead of just calling a method you are now instantiating an object and then Now if you visit `http://localhost:8000/` everything should work. If not, go back and debug. And of course don't forget to commit your changes. -[<< previous](5-router.md) | [next >>](7-inversion-of-control.md) \ No newline at end of file +[<< previous](05-router.md) | [next >>](07-inversion-of-control.md) \ No newline at end of file diff --git a/7-inversion-of-control.md b/07-inversion-of-control.md similarity index 93% rename from 7-inversion-of-control.md rename to 07-inversion-of-control.md index 1192545..2200da5 100644 --- a/7-inversion-of-control.md +++ b/07-inversion-of-control.md @@ -1,4 +1,4 @@ -[<< previous](6-dispatching-to-a-class.md) | [next >>](8-dependency-injector.md) +[<< previous](06-dispatching-to-a-class.md) | [next >>](08-dependency-injector.md) ### Inversion of Control @@ -48,4 +48,4 @@ The `Http\HttpResponse` object implements the `Http\Response` interface, so it f Now everything should work again. But if you follow this example, all your objects that are instantiated this way will have the same objects injected. This is of course not good, so let's fix that in the next part. -[<< previous](6-dispatching-to-a-class.md) | [next >>](8-dependency-injector.md) \ No newline at end of file +[<< previous](06-dispatching-to-a-class.md) | [next >>](08-dependency-injector.md) \ No newline at end of file diff --git a/8-dependency-injector.md b/08-dependency-injector.md similarity index 95% rename from 8-dependency-injector.md rename to 08-dependency-injector.md index f4939af..ffd2a61 100644 --- a/8-dependency-injector.md +++ b/08-dependency-injector.md @@ -1,4 +1,4 @@ -[<< previous](7-inversion-of-control.md) | [next >>](9-templating.md) +[<< previous](07-inversion-of-control.md) | [next >>](09-templating.md) ### Dependency Injector @@ -94,4 +94,4 @@ As you can see now the class has two dependencies. Try to access the page with a Congratulations, you have now successfully laid the groundwork for your application. -[<< previous](7-inversion-of-control.md) | [next >>](9-templating.md) \ No newline at end of file +[<< previous](07-inversion-of-control.md) | [next >>](09-templating.md) \ No newline at end of file diff --git a/9-templating.md b/09-templating.md similarity index 97% rename from 9-templating.md rename to 09-templating.md index 373303f..4165ffe 100644 --- a/9-templating.md +++ b/09-templating.md @@ -1,4 +1,4 @@ -[<< previous](8-dependency-injector.md) | [next >>](10-dynamic-pages.md) +[<< previous](08-dependency-injector.md) | [next >>](10-dynamic-pages.md) ### Templating @@ -136,4 +136,4 @@ Now you can go back to your `Homepage` controller and change the render line to Navigate to the hello page in your browser to make sure everything works. And as always, don't forget to commit your changes. -[<< previous](8-dependency-injector.md) | [next >>](10-dynamic-pages.md) +[<< previous](08-dependency-injector.md) | [next >>](10-dynamic-pages.md) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index 9d2b789..7a9ae70 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -1,4 +1,4 @@ -[<< previous](9-templating.md) +[<< previous](09-templating.md) ### Dynamic Pages @@ -223,4 +223,4 @@ Try a few different URLs to check that everything is working as it should. If so And as always, don't forget to commit your changes. -[<< previous](9-templating.md) \ No newline at end of file +[<< previous](09-templating.md) \ No newline at end of file diff --git a/README.md b/README.md index 03b3de6..3d02a08 100644 --- a/README.md +++ b/README.md @@ -10,17 +10,17 @@ I saw a lot of people coming into the Stack Overflow PHP chatroom and asking if So my goal with this is to provide an easy resource that people can be pointed to. In most cases a framework does not make sense and writing an application from scratch with the help of some third party packages is much, much easier than some people think. -So let's get started right away with the [first part](1-front-controller.md). +So let's get started right away with the [first part](01-front-controller.md). ### Parts -1. [Front Controller](1-front-controller.md) -2. [Composer](2-composer.md) -3. [Error Handler](3-error-handler.md) -4. [HTTP](4-http.md) -5. [Router](5-router.md) -6. [Dispatching to a Class](6-dispatching-to-a-class.md) -7. [Inversion of Control](7-inversion-of-control.md) -8. [Dependency Injector](8-dependency-injector.md) -9. [Templating](9-templating.md) +1. [Front Controller](01-front-controller.md) +2. [Composer](02-composer.md) +3. [Error Handler](03-error-handler.md) +4. [HTTP](04-http.md) +5. [Router](05-router.md) +6. [Dispatching to a Class](06-dispatching-to-a-class.md) +7. [Inversion of Control](07-inversion-of-control.md) +8. [Dependency Injector](08-dependency-injector.md) +9. [Templating](09-templating.md) 10. [Dynamic Pages](10-dynamic-pages.md) From a99778b0495002f5102f791bc57ec368ad3f2755 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 14 Jan 2015 20:54:18 +0100 Subject: [PATCH 047/314] rename engine to renderer, solves #15 --- 09-templating.md | 28 +++++++++++++++------------- 10-dynamic-pages.md | 14 +++++++------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/09-templating.md b/09-templating.md index 4165ffe..68b895c 100644 --- a/09-templating.md +++ b/09-templating.md @@ -24,20 +24,20 @@ First let's define the interface that we want. Remember the [interface segregati So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a new folder in your `src/` folder with the name `Template` where you can put all the template related things. -In there create a new interface `Engine.php` that looks like this: +In there create a new interface `Renderer.php` that looks like this: ```php alias('Example\Template\Engine', 'Example\Template\MustacheEngineAdapter');` +`$injector->alias('Example\Template\Renderer', 'Example\Template\MustacheRenderer');` Now in your `Homepage` controller, add the new dependency like this: @@ -77,29 +77,27 @@ namespace Example\Controllers; use Http\Request; use Http\Response; -use Example\Template\Engine as TemplateEngine; +use Example\Template\Renderer; class Homepage { private $request; private $response; - private $templateEngine; + private $renderer; public function __construct( Request $request, Response $response, - TemplateEngine $templateEngine + Renderer $renderer ) { $this->request = $request; $this->response = $response; - $this->templateEngine = $templateEngine; + $this->renderer = $renderer; } ... ``` -As you can see I imported the engine with an alias. Without the full namespace it would be relatively unclear what a class does if it is just referenced by `Engine`. Also, another part of the application might also have a class with the name `Engine`. So to avoid that I give it a short and descriptive alias. - We also have to rewrite the `show` method. Please note that while we are just passing in a simple array, Mustache also gives you the option to pass in a view context object. We will go over this later, for now let's keep it as simple as possible. ```php @@ -108,7 +106,7 @@ We also have to rewrite the `show` method. Please note that while we are just pa $data = [ 'name' => $this->request->getParameter('name', 'stranger'), ]; - $html = $this->templateEngine->render('Hello {{name}}', $data); + $html = $this->renderer->render('Hello {{name}}', $data); $this->response->setContent($html); } ``` @@ -132,7 +130,11 @@ In your project root folder, create a `templates` folder. In there, create a fil Hello {{ name }} ``` -Now you can go back to your `Homepage` controller and change the render line to `$content = $this->templateEngine->render('Homepage', $data);` +Now you can go back to your `Homepage` controller and change the render line to `$content = $this->renderer + + + +->render('Homepage', $data);` Navigate to the hello page in your browser to make sure everything works. And as always, don't forget to commit your changes. diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index 7a9ae70..7da2eb5 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -107,12 +107,12 @@ public function show($params) { $slug = $params['slug']; $data['content'] = $this->pageReader->getContentBySlug($slug); - $html = $this->templateEngine->render('Page', $data); + $html = $this->renderer->render('Page', $data); $this->response->setContent($html); } ``` -To make this work, we will need to inject a `Response`, `TemplateEngine` and a `PageReader`. I will leave this to you as an exercise. Remember to `use` all the proper namespaces. Use the `Homepage` controller as a reference. +To make this work, we will need to inject a `Response`, `Renderer` and a `PageReader`. I will leave this to you as an exercise. Remember to `use` all the proper namespaces. Use the `Homepage` controller as a reference. Did you get everything to work? @@ -124,22 +124,22 @@ If not, this is how the beginning of your controller should look now: namespace Example\Controllers; use Http\Response; -use Example\Template\Engine as TemplateEngine; +use Example\Template\Renderer; use Example\Page\PageReader; class Page { private $response; - private $templateEngine; + private $renderer; private $pageReader; public function __construct( Response $response, - TemplateEngine $templateEngine, + Renderer $renderer, PageReader $pageReader ) { $this->response = $response; - $this->templateEngine = $templateEngine; + $this->renderer = $renderer; $this->pageReader = $pageReader; } ... @@ -207,7 +207,7 @@ public function show($params) return $this->response->setContent('404 - Page not found'); } - $html = $this->templateEngine->render('Page', $data); + $html = $this->renderer->render('Page', $data); $this->response->setContent($html); } ``` From 03c012ece56c1ac4a8ca692bfdfe0b13851df3f5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 14 Jan 2015 21:04:27 +0100 Subject: [PATCH 048/314] renamed method to readBySlug. closes #14 --- 10-dynamic-pages.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index 7da2eb5..6e5db14 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -48,7 +48,7 @@ namespace Example\Page; interface PageReader { - public function getContentBySlug($slug); + public function readBySlug($slug); } ``` @@ -73,7 +73,7 @@ class FilePageReader implements PageReader $this->pageFolder = $pageFolder; } - public function getContentBySlug($slug) + public function readBySlug($slug) { return 'I am a placeholder'; } @@ -106,7 +106,7 @@ Now go back to the `Page` controller and change the `show` method to the followi public function show($params) { $slug = $params['slug']; - $data['content'] = $this->pageReader->getContentBySlug($slug); + $data['content'] = $this->pageReader->readBySlug($slug); $html = $this->renderer->render('Page', $data); $this->response->setContent($html); } @@ -150,7 +150,7 @@ So far so good, now let's make our `FilePageReader` actually do some work. Again, let's check first that the proper type was passed into the method: ```php -public function getContentBySlug($slug) +public function readBySlug($slug) { if (!is_string($slug)) { throw new InvalidArgumentException('slug must be a string'); @@ -177,7 +177,7 @@ class InvalidPageException extends Exception } ``` -Then in the `FilePageReader` file add this code at the end of your `getContentBySlug` method: +Then in the `FilePageReader` file add this code at the end of your `readBySlug` method: ``` $path = "$this->pageFolder/$slug.md"; @@ -201,7 +201,7 @@ public function show($params) $slug = $params['slug']; try { - $data['content'] = $this->pageReader->getContentBySlug($slug); + $data['content'] = $this->pageReader->readBySlug($slug); } catch (InvalidPageException $e) { $this->response->setStatusCode(404); return $this->response->setContent('404 - Page not found'); From c64693072875db4121bd8a5608e5a818405cb29e Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 14 Jan 2015 21:09:38 +0100 Subject: [PATCH 049/314] make version requirement more clear. resolves #12 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3d02a08..411e3cb 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ I saw a lot of people coming into the Stack Overflow PHP chatroom and asking if So my goal with this is to provide an easy resource that people can be pointed to. In most cases a framework does not make sense and writing an application from scratch with the help of some third party packages is much, much easier than some people think. +**This tutorial was written with at least PHP 5.5 in mind.** If you are using a lower version, please upgrade it before you start. I recommend that you use the [current stable version](http://php.net/downloads.php). + So let's get started right away with the [first part](01-front-controller.md). ### Parts From e23ef871c8d3b7a12a2a4b273b7c27978eed3f5a Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 14 Jan 2015 21:10:28 +0100 Subject: [PATCH 050/314] make version requirement more clear. resolves #12 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 411e3cb..33bc1e5 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ I saw a lot of people coming into the Stack Overflow PHP chatroom and asking if So my goal with this is to provide an easy resource that people can be pointed to. In most cases a framework does not make sense and writing an application from scratch with the help of some third party packages is much, much easier than some people think. -**This tutorial was written with at least PHP 5.5 in mind.** If you are using a lower version, please upgrade it before you start. I recommend that you use the [current stable version](http://php.net/downloads.php). +**This tutorial was written for PHP 5.5 or newer versions.** If you are using a lower version, please upgrade it before you start. I recommend that you use the [current stable version](http://php.net/downloads.php). So let's get started right away with the [first part](01-front-controller.md). From 79e64e3205611a62eddc71b5482b7c94ea88e7c1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 14 Jan 2015 21:10:53 +0100 Subject: [PATCH 051/314] make version requirement more clear. resolves #12 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 33bc1e5..9fe45b2 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ I saw a lot of people coming into the Stack Overflow PHP chatroom and asking if So my goal with this is to provide an easy resource that people can be pointed to. In most cases a framework does not make sense and writing an application from scratch with the help of some third party packages is much, much easier than some people think. -**This tutorial was written for PHP 5.5 or newer versions.** If you are using a lower version, please upgrade it before you start. I recommend that you use the [current stable version](http://php.net/downloads.php). +**This tutorial was written for PHP 5.5 or newer versions.** If you are using an older version, please upgrade it before you start. I recommend that you use the [current stable version](http://php.net/downloads.php). So let's get started right away with the [first part](01-front-controller.md). From 31b9f9f57f3906ef0e2d1c77630d81e5b50e54ff Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 14 Jan 2015 21:16:39 +0100 Subject: [PATCH 052/314] explain __DIR__. solves #13 --- 01-front-controller.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/01-front-controller.md b/01-front-controller.md index 349c513..75315ea 100644 --- a/01-front-controller.md +++ b/01-front-controller.md @@ -22,6 +22,8 @@ Inside the `public` folder you can now create your `index.php`. Remember that yo require __DIR__ . '/../src/Bootstrap.php'; ``` +`__DIR__` is a [magic constant](http://php.net/manual/en/language.constants.predefined.php) that contains the path of the directory. By using it, you can make sure that the `require` always uses the same relative path to the file it is used in. Otherwise, if you call the `index.php` from a different folder it will not find the file. + The `Bootstrap.php` will be the file that wires your application together. We will get to it shortly. The rest of the public folder is reserved for your public asset files (like JavaScript files and stylesheets). From 967bb2bdec3904169530034f396e00a34bcbd215 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 21 Jan 2015 23:45:56 +0100 Subject: [PATCH 053/314] begin next chapter --- 10-dynamic-pages.md | 4 ++-- 11-page-menu.md | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 11-page-menu.md diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index 6e5db14..2f9783e 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -1,4 +1,4 @@ -[<< previous](09-templating.md) +[<< previous](09-templating.md) | [next >>](11-page-menu.md) ### Dynamic Pages @@ -223,4 +223,4 @@ Try a few different URLs to check that everything is working as it should. If so And as always, don't forget to commit your changes. -[<< previous](09-templating.md) \ No newline at end of file +[<< previous](09-templating.md) | [next >>](11-page-menu.md) \ No newline at end of file diff --git a/11-page-menu.md b/11-page-menu.md new file mode 100644 index 0000000..6c1f43e --- /dev/null +++ b/11-page-menu.md @@ -0,0 +1,10 @@ +[<< previous](10-dynamic-pages.md) + +### Page Menu + +Now we have some sweet dynamic pages. But nobody can find them. + +Let's fix that now. In this chapter we will create a menu with links to all our pages. + + +[<< previous](10-dynamic-pages.md) \ No newline at end of file From 84ba9a1654ca0125257e3333db3d09dae32251e4 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Wed, 28 Jan 2015 22:26:47 +0100 Subject: [PATCH 054/314] update header() call to fix overwrite header bug --- 04-http.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/04-http.md b/04-http.md index 660eece..cc33fe3 100644 --- a/04-http.md +++ b/04-http.md @@ -35,7 +35,7 @@ To actually send something back, you will also need to add the following snippet ```php foreach ($response->getHeaders() as $header) { - header($header); + header($header, false); } echo $response->getContent(); @@ -43,6 +43,8 @@ echo $response->getContent(); This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only stores data. This is handled differently by most other HTTP components where the classes send data back to the browser as a side-effect, so keep that in mind if you use another component. +The second parameter of `header()` is false because otherwise existing headers will be overwritten. + Right now it is just sending an empty response back to the browser with the status code `200`; to change that, add the following code between the code snippets from above: ```php From d9738776b2c02839366ef27218670c3396d39a23 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 8 Feb 2015 18:00:41 +0100 Subject: [PATCH 055/314] added more to menu chapter --- 09-templating.md | 2 +- 11-page-menu.md | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/09-templating.md b/09-templating.md index 68b895c..f485a09 100644 --- a/09-templating.md +++ b/09-templating.md @@ -8,7 +8,7 @@ A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install that package before you continue. -Other well known alternatives would be [Twig](http://twig.sensiolabs.org/) or [Smarty](http://www.smarty.net/), but they are both pretty bloated and offer too much functionality for just a template engine. +Another well known alternative would be [Twig](http://twig.sensiolabs.org/). Now please go and have a look at the source code of the [engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class does not implement an interface. diff --git a/11-page-menu.md b/11-page-menu.md index 6c1f43e..990496b 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -6,5 +6,26 @@ Now we have some sweet dynamic pages. But nobody can find them. Let's fix that now. In this chapter we will create a menu with links to all our pages. +For a start we will just send a hardcoded array to the template. Go to you `Homepage` controller and change your `$data` array to this: + +```php +$data = [ + 'name' => $this->request->getParameter('name', 'stranger'), + 'menuItems' => ['href' => '/', 'text' => 'Homepage'], +]; +``` + +Now add the following at the top of your `Homepage.mustache` file: + +``` +{{#menuItems}} + {{ text }}
+{{/menuItems}} +``` + +Now if you navigate to your homepage, you should see a link at the top. + +So far so good. But now we realize that we want to reuse this code snippet on every page. We could create a separate file and include it every time, but there is a better solution. + [<< previous](10-dynamic-pages.md) \ No newline at end of file From 20aef6e7417ca21de80ff2884e5ec6b5c5d60592 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 8 Feb 2015 20:16:44 +0100 Subject: [PATCH 056/314] expanded menu chapter and refactored old chapters --- 09-templating.md | 8 +++-- 10-dynamic-pages.md | 2 +- 11-page-menu.md | 72 ++++++++++++++++++++++++++++++++++++++------- 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/09-templating.md b/09-templating.md index f485a09..d597228 100644 --- a/09-templating.md +++ b/09-templating.md @@ -118,12 +118,16 @@ To make this change we need to pass an options array to the `Mustache_Engine` co ```php $injector->define('Mustache_Engine', [ ':options' => [ - 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__DIR__) . '/templates'), + 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__DIR__) . '/templates', [ + 'extension' => '.html', + ]), ], ]); ``` -In your project root folder, create a `templates` folder. In there, create a file `Homepage.mustache`. The content of the file should look like this: +We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have to rename all the template files. + +In your project root folder, create a `templates` folder. In there, create a file `Homepage.html`. The content of the file should look like this: ```

Hello World

diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index 2f9783e..8565eb7 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -86,7 +86,7 @@ You could also put the page related things into it's own package and reuse it in Because PHP does not have the ability to type hint for scalar values (things like strings and integers), we have to manually check that `$pageFolder` is a string. If we don't do that, there might be a bug in the future that is hard to find if a wrong type is injected. By throwing an exception, this can be caught and debugged immediately. -This will do for now. Let's create a template file for our pages with the name `Page.mustache` in the `templates` folder. For now just add `{{ content }}` in there. +This will do for now. Let's create a template file for our pages with the name `Page.html` in the `templates` folder. For now just add `{{ content }}` in there. Add the following to your `Dependencies.php` file so that the application know which implementation to inject for our new interface. We also define the the `pageFolder` there. diff --git a/11-page-menu.md b/11-page-menu.md index 990496b..2d2ca2e 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -6,26 +6,78 @@ Now we have some sweet dynamic pages. But nobody can find them. Let's fix that now. In this chapter we will create a menu with links to all our pages. -For a start we will just send a hardcoded array to the template. Go to you `Homepage` controller and change your `$data` array to this: +When we have a menu, we will want to be able to reuse the same code on multiple page. We could create a separate file and include it every time, but there is a better solution. + +It is more practical to have templates that are able to extend other templates, like a layout for example. Then we can have all the layout related code in a single file and we don't have to include header and footer files in every template. + +Sadly our implementation of mustache does not support this. We could write code to work around this, which will take time and could introduce some bugs. Or we could switch to a library that already supports this and is well tested. [Twig](http://twig.sensiolabs.org/) for example. + +Now you might wonder why we didn't start with Twig right away. This is a good example to show why using interfaces and writing loosely-coupled code is a good idea. + +Remember how you created a `MustacheRenderer` in [chapter 9](09-templating.md)? This time, we create a `TwigRenderer` that implements the same interface. + +But before we start, install the latest version of Twig with composer. + +Then create the a `TwigRenderer.php` in your `src/Template` folder that looks like this: + +```php +renderer = $renderer; + } + + public function render($template, $data = []) + { + return $this->renderer->render("$template.html", $data); + } +} +``` + +As you can see, on the render function call a `.html` is added. This is because Twig does not add a file ending by default and you would have to specifiy it on every call otherwise. By doing it like this, you can use it in the same way as you used Mustache. + +Add the following code to your `Dependencies.php` file: + +```php +$injector->delegate('Twig_Environment', function() use ($injector) { + $loader = new Twig_Loader_Filesystem(dirname(__DIR__) . '/templates'); + $twig = new Twig_Environment($loader); + return $twig; +}); +``` + +Instead of just defining the dependencies, we are using a delegate to give the responsibility to create the class to a function. This will be useful in the future. + +Now you can switch the `Renderer` alias from `MustacheRenderer` to `TwigRenderer`. Now by default Twig will be used instead of Mustache. + +If you have a look at the site in your browser, everything should work now as before. Now let's get started with the actual menu. + +To start we will just send a hardcoded array to the template. Go to you `Homepage` controller and change your `$data` array to this: ```php $data = [ 'name' => $this->request->getParameter('name', 'stranger'), - 'menuItems' => ['href' => '/', 'text' => 'Homepage'], + 'menuItems' => 'menuItems' => [['href' => '/', 'text' => 'Homepage']], ]; ``` -Now add the following at the top of your `Homepage.mustache` file: +At the top of your `Homepage.html` file add this code: ``` -{{#menuItems}} - {{ text }}
-{{/menuItems}} +{% for item in menuItems %} + {{ item.text }}
+{% endfor %} ``` -Now if you navigate to your homepage, you should see a link at the top. - -So far so good. But now we realize that we want to reuse this code snippet on every page. We could create a separate file and include it every time, but there is a better solution. - +Now if you refresh the homepage in the browser, you should see a link. [<< previous](10-dynamic-pages.md) \ No newline at end of file From c9b9fb2d1269d908a104ea9bd5377c5ca1f9f970 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 8 Feb 2015 20:51:22 +0100 Subject: [PATCH 057/314] continued menu chapter --- 11-page-menu.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/11-page-menu.md b/11-page-menu.md index 2d2ca2e..48ff549 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -80,4 +80,6 @@ At the top of your `Homepage.html` file add this code: Now if you refresh the homepage in the browser, you should see a link. +to be continued... + [<< previous](10-dynamic-pages.md) \ No newline at end of file From 589585cde40523d66772a3053c0bb228d1c6412c Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 8 Feb 2015 20:53:31 +0100 Subject: [PATCH 058/314] added README entry for new chapter --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9fe45b2..49738f9 100644 --- a/README.md +++ b/README.md @@ -26,3 +26,4 @@ So let's get started right away with the [first part](01-front-controller.md). 8. [Dependency Injector](08-dependency-injector.md) 9. [Templating](09-templating.md) 10. [Dynamic Pages](10-dynamic-pages.md) +11. [Page Menu](11-page-menu.md) From 24ca27483647339467ddda23e05889e5209c780c Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 13 Feb 2015 14:48:12 +0100 Subject: [PATCH 059/314] added layout file --- 11-page-menu.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/11-page-menu.md b/11-page-menu.md index 48ff549..5e1f7e4 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -80,6 +80,44 @@ At the top of your `Homepage.html` file add this code: Now if you refresh the homepage in the browser, you should see a link. +The menu works on the homepage, but we want it on all our pages. We could copy it over to all the template files, but that would be a bad idea. Then if something changes, you would have to go change all the files. + +So instead we are going to use a layout that can be used by all the templates. + +Create a `Layout.html` in your `templates` folder with the following content: + +```php +{% for item in menuItems %} + {{ item['text'] }}
+{% endfor %} +
+{% block content %} +{% endblock %} +``` + +Then change your `Homepage.html` to this: + +```php +{% extends "Layout.html" %} +{% block content %} +

Hello World

+ Hello {{ name }} +{% endblock %} +``` + +And your `Page.html` to this: + +```php +{% extends "Layout.html" %} +{% block content %} + {{ content }} +{% endblock %} +``` + +If you refresh your homepage now, you should see the menu. But if you go to a subpage, the menu is not there but the `
` line is. + +The problem is that we are only passing the `menuItems` to the homepage. Doing that over and over again for all pages would be a bit tedious and a lot of work if something changes. So let's fix that in the next step. + to be continued... [<< previous](10-dynamic-pages.md) \ No newline at end of file From dab90c5b2422936c1b9214a71e9d2fc79807ea10 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 13 Feb 2015 16:36:04 +0100 Subject: [PATCH 060/314] frontend renderer --- 11-page-menu.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/11-page-menu.md b/11-page-menu.md index 5e1f7e4..ea0a0b5 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -72,7 +72,7 @@ $data = [ At the top of your `Homepage.html` file add this code: -``` +```php {% for item in menuItems %} {{ item.text }}
{% endfor %} @@ -118,6 +118,64 @@ If you refresh your homepage now, you should see the menu. But if you go to a su The problem is that we are only passing the `menuItems` to the homepage. Doing that over and over again for all pages would be a bit tedious and a lot of work if something changes. So let's fix that in the next step. -to be continued... +We could create a global variable that is usable by all templates, but that is not a good idea here. We will add different parts of the site in the future like an admin area and we will have a different menu there. +So instead we will use a custom renderer for the frontend. First we create an empty interface that extends the existing `Renderer` interface. + +```php +renderer = $renderer; + } + + public function render($template, $data = []) + { + $data = array_merge($data, [ + 'menuItems' => [['href' => '/', 'text' => 'Homepage']], + ]); + return $this->renderer->render($template, $data); + } +} +``` + +As you can see we have a dependency on a `Renderer` in this class. This class is a wrapper for our `Renderer` and adds the `menuItems` to all `$data` arrays. + +Of course we also need to add another alias to the dependencies file. + +```php +$injector->alias('Example\Template\FrontendRenderer', 'Example\Template\FrontendTwigRenderer'); +``` + +Now go to your controllers and exchange all references of `Renderer` with `FrontendRenderer`. Make sure you change it in both the `use` statement at the top and in the constructor. + +Also delete the following line from the `Homepage` controller: + +```php +'menuItems' => [['href' => '/', 'text' => 'Homepage']], +``` + +Once that is done, you should see the menu on both the homepage and your subpages. + +to be continued... [<< previous](10-dynamic-pages.md) \ No newline at end of file From 047f34863de41667478aefd079b5ee5fec4a9a6d Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 13 Feb 2015 16:36:27 +0100 Subject: [PATCH 061/314] frontend renderer --- 11-page-menu.md | 1 + 1 file changed, 1 insertion(+) diff --git a/11-page-menu.md b/11-page-menu.md index ea0a0b5..e8baaa4 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -178,4 +178,5 @@ Also delete the following line from the `Homepage` controller: Once that is done, you should see the menu on both the homepage and your subpages. to be continued... + [<< previous](10-dynamic-pages.md) \ No newline at end of file From 86064842f4d9bbb6d60b7b25fa68f14e957c8a83 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 14 Feb 2015 01:07:31 +0100 Subject: [PATCH 062/314] fixed typo --- 11-page-menu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/11-page-menu.md b/11-page-menu.md index e8baaa4..cc33875 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -66,7 +66,7 @@ To start we will just send a hardcoded array to the template. Go to you `Homepag ```php $data = [ 'name' => $this->request->getParameter('name', 'stranger'), - 'menuItems' => 'menuItems' => [['href' => '/', 'text' => 'Homepage']], + 'menuItems' => [['href' => '/', 'text' => 'Homepage']], ]; ``` From b1add840723810cb594a02bdac3387fe69c113f1 Mon Sep 17 00:00:00 2001 From: burki94 Date: Mon, 6 Apr 2015 13:12:37 +0200 Subject: [PATCH 063/314] Update 08-dependency-injector.md Auryn\Provider was replaced with Auryn\Injector --- 08-dependency-injector.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/08-dependency-injector.md b/08-dependency-injector.md index ffd2a61..d2dd08e 100644 --- a/08-dependency-injector.md +++ b/08-dependency-injector.md @@ -11,7 +11,7 @@ Install the Auryn package and then create a new file called `Dependencies.php` i ```php alias('Http\Response', 'Http\HttpResponse'); $injector->share('Http\HttpRequest'); @@ -94,4 +94,4 @@ As you can see now the class has two dependencies. Try to access the page with a Congratulations, you have now successfully laid the groundwork for your application. -[<< previous](07-inversion-of-control.md) | [next >>](09-templating.md) \ No newline at end of file +[<< previous](07-inversion-of-control.md) | [next >>](09-templating.md) From f7344a1f886a66992875ce5fc0c87dff474e3949 Mon Sep 17 00:00:00 2001 From: Kier Borromeo Date: Tue, 14 Apr 2015 15:54:42 +0800 Subject: [PATCH 064/314] interface to class This seems to be a typo. No? --- 09-templating.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/09-templating.md b/09-templating.md index d597228..f023224 100644 --- a/09-templating.md +++ b/09-templating.md @@ -16,7 +16,7 @@ You could just type hint against the concrete class. But the problem with this a In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want to add functionality to the engine. You can't do that without going back and changing all your code that is tightly coupled. -What we want is loose coupling. We will type hint against an interface that implements an interface. So if you need another implementation, you just implement that interface in your new class and inject the new class instead. +What we want is loose coupling. We will type hint against a class that implements an interface. So if you need another implementation, you just implement that interface in your new class and inject the new class instead. Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). This sounds a lot more complicated than it is, so just follow along. From f2c5137ae1464f55bd5ba9fe0d188e57abd4d71d Mon Sep 17 00:00:00 2001 From: Kevin M Granger Date: Thu, 23 Jul 2015 15:09:52 -0700 Subject: [PATCH 065/314] Fix typo in 05-router.md Routers.php -> Routes.php --- 05-router.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/05-router.md b/05-router.md index a381107..d635a58 100644 --- a/05-router.md +++ b/05-router.md @@ -74,6 +74,6 @@ $routeDefinitionCallback = function (\FastRoute\RouteCollector $r) { $dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); ``` -This is already an improvement, but now all the handler code is in the `Routers.php` file. This is not optimal, so let's fix that in the next part. +This is already an improvement, but now all the handler code is in the `Routes.php` file. This is not optimal, so let's fix that in the next part. [<< previous](04-http.md) | [next >>](06-dispatching-to-a-class.md) From e36b01ba2961eaec5d451fea7716a505e89d7e00 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 2 Sep 2015 19:25:43 +0200 Subject: [PATCH 066/314] finished chapter --- 11-page-menu.md | 80 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/11-page-menu.md b/11-page-menu.md index cc33875..5f8039d 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -175,8 +175,84 @@ Also delete the following line from the `Homepage` controller: 'menuItems' => [['href' => '/', 'text' => 'Homepage']], ``` -Once that is done, you should see the menu on both the homepage and your subpages. +Once that is done, you should see the menu on both the homepage and your subpages. -to be continued... +Everything should work now, but it doesn't really make sense that the menu is defined in the `FrontendTwigRenderer`. So let's refactor that and move it into it's own class. + +Right now the menu is defined in the array, but it is very likely that this will change in the future. Maybe you want to define it in the database or maybe you even want to generate it dynamically based on the pages available. We don't have this information and things might change in the future. + +So let's do the right thing here and start with an interface again. But first, create a new order in the `src` directory for the menu related things. `Menu` sounds like a reasonable name, doesn't it? + +```php + '/', 'text' => 'Homepage']]; + } +} +``` + +This is only a temporary solution to keep things moving forward. We are going to revisit this later. + +Before we continue, let's edit the dependencies file to make sure that our application knows which implementation to use when the interface is requested. + +Add these lines above the `return` statement: + +```php +$injector->alias('Example\Menu\MenuReader', 'Example\Menu\ArrayMenuReader'); +$injector->share('Example\Menu\FileMenuReader'); +``` + +Now you need to change out the hardcoded array in the `FrontendTwigRenderer` class to make it use our new `MenuReader` instead. Give it a try without looking at the solution below. + +Did you finish it or did you get stuck? Or are you just lazy? Doesn't matter, here is a working solution: + +```php +renderer = $renderer; + $this->menuReader = $menuReader; + } + + public function render($template, $data = []) + { + $data = array_merge($data, [ + 'menuItems' => $this->menuReader->readMenu(), + ]); + return $this->renderer->render($template, $data); + } +} +``` + +Everything still working? Awesome. Commit everything and move on to the next chapter. [<< previous](10-dynamic-pages.md) \ No newline at end of file From feb010bf3bd0b9d1451d5f09599f3f06ce85328f Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Sep 2015 21:52:31 +0200 Subject: [PATCH 067/314] frontend --- 11-page-menu.md | 8 +- 12-frontend.md | 188 +++++++++++++++++++++++++++++++++++++++++++++ to-be-continued.md | 14 ++++ 3 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 12-frontend.md create mode 100644 to-be-continued.md diff --git a/11-page-menu.md b/11-page-menu.md index 5f8039d..c46782b 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -1,4 +1,4 @@ -[<< previous](10-dynamic-pages.md) +[<< previous](10-dynamic-pages.md) | [next >>](12-frontend.md) ### Page Menu @@ -205,7 +205,9 @@ class ArrayMenuReader implements MenuReader { public function readMenu() { - return [['href' => '/', 'text' => 'Homepage']]; + return [ + ['href' => '/', 'text' => 'Homepage'], + ]; } } ``` @@ -255,4 +257,4 @@ class FrontendTwigRenderer implements FrontendRenderer Everything still working? Awesome. Commit everything and move on to the next chapter. -[<< previous](10-dynamic-pages.md) \ No newline at end of file +[<< previous](10-dynamic-pages.md) | [next >>](12-frontend.md) \ No newline at end of file diff --git a/12-frontend.md b/12-frontend.md new file mode 100644 index 0000000..620d878 --- /dev/null +++ b/12-frontend.md @@ -0,0 +1,188 @@ +[<< previous](11-page-menu.md) | [next >>](to-be-continued.md) + + +### Frontend + +I don't know about you, but I don't like to work on a site that looks two decades old. So let's improve the look of our little application. + +This is not a frontend tutorial, so we'll just [pure](http://purecss.io/) and call it a day. + +First we need to change the `Layout.html` template. I don't want to waste your time with HTML and CSS, so I'll just provide the code for you to copy paste. + +```php + + + + + Example + + + + +
+ +
+
+ {% block content %} + {% endblock %} +
+
+
+ + +``` + +You will also need some custom CSS. This is a file that we want publicly accessible. So where do we put it? Exactly, in the public folder. + +But to keep things a little organized, add a `css` folder in there first and then create a `style.css` with the following content: + +```css +body { + color: #777; +} + +#layout { + position: relative; + padding-left: 0; +} + +#layout.active #menu { + left: 150px; + width: 150px; +} + +#layout.active .menu-link { + left: 150px; +} + +.content { + margin: 0 auto; + padding: 0 2em; + max-width: 800px; + margin-bottom: 50px; + line-height: 1.6em; +} + +.header { + margin: 0; + color: #333; + text-align: center; + padding: 2.5em 2em 0; + border-bottom: 1px solid #eee; +} + +.header h1 { + margin: 0.2em 0; + font-size: 3em; + font-weight: 300; +} + +.header h2 { + font-weight: 300; + color: #ccc; + padding: 0; + margin-top: 0; +} + +#menu { + margin-left: -150px; + width: 150px; + position: fixed; + top: 0; + left: 0; + bottom: 0; + z-index: 1000; + background: #191818; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + +#menu a { + color: #999; + border: none; + padding: 0.6em 0 0.6em 0.6em; +} + +#menu .pure-menu, +#menu .pure-menu ul { + border: none; + background: transparent; +} + +#menu .pure-menu ul, +#menu .pure-menu .menu-item-divided { + border-top: 1px solid #333; +} + +#menu .pure-menu li a:hover, +#menu .pure-menu li a:focus { + background: #333; +} + +#menu .pure-menu-selected, +#menu .pure-menu-heading { + background: #1f8dd6; +} + +#menu .pure-menu-selected a { + color: #fff; +} + +#menu .pure-menu-heading { + font-size: 110%; + color: #fff; + margin: 0; +} + +.header, +.content { + padding-left: 2em; + padding-right: 2em; +} + +#layout { + padding-left: 150px; /* left col width "#menu" */ + left: 0; +} +#menu { + left: 150px; +} + +.menu-link { + position: fixed; + left: 150px; + display: none; +} + +#layout.active .menu-link { + left: 150px; +} +``` + +Now if you have a look at your site again, things should look a little better. Feel free to further improve the look of it yourself later. But let's continue with the tutorial now. + +Every time that you need an asset or a file publicly available, then you can just put it in your `public` folder. You will need this for all kinds of assets like javascript files, css files, images and more. + +So far so good, but it would be nice if our visitors can see what page they are on. + +Of course we need more than one page in the menu for this. I will just use the `page-one.md` that we created earlier in the tutorial. But feel free to add a few more pages and add them as well. + +Go back to the `ArrayMenuReader` and add your new pages to the array. It should look something like this now: + +```php +return [ + ['href' => '/', 'text' => 'Homepage'], + ['href' => '/page-one', 'text' => 'Page One'], +]; +``` + +[<< previous](11-page-menu.md) | [next >>](to-be-continued.md) + diff --git a/to-be-continued.md b/to-be-continued.md new file mode 100644 index 0000000..b595e24 --- /dev/null +++ b/to-be-continued.md @@ -0,0 +1,14 @@ +### To be continued... + +Congratulations. You made it this far. + +I hope you were following the tutorial step by step and not just skipping over the chapter :) + +I have received good feedback so far so I decided to start writing a book. If you want to learn more and stay updated, [click here](http://artofphp.com/). + +But don't worry, I will also keep working on this tutorial. I was a bit lazy over the summer but now that it getting colder again I will have much more time to spend on the tutorial. + +If you got something out of the tutorial I would appreciate a star. It's the only way for me to see if people are actually reading this :) + +Thanks for your time, +Patrick \ No newline at end of file From 98b1fd8ce7ecb9ebc4f3c1194758eb9d721935d0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Sep 2015 21:54:13 +0200 Subject: [PATCH 068/314] tbc typo --- to-be-continued.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/to-be-continued.md b/to-be-continued.md index b595e24..598616c 100644 --- a/to-be-continued.md +++ b/to-be-continued.md @@ -2,9 +2,9 @@ Congratulations. You made it this far. -I hope you were following the tutorial step by step and not just skipping over the chapter :) +I hope you were following the tutorial step by step and not just skipping over the chapters :) -I have received good feedback so far so I decided to start writing a book. If you want to learn more and stay updated, [click here](http://artofphp.com/). +I have received good feedback so far so I decided to start writing a book. [Click here](http://artofphp.com/) if you want to learn more or stay updated with how the book is coming along. But don't worry, I will also keep working on this tutorial. I was a bit lazy over the summer but now that it getting colder again I will have much more time to spend on the tutorial. From 196515f3bdf8520e64929c7eba3c3e94a7a56cd9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Sep 2015 21:54:53 +0200 Subject: [PATCH 069/314] tbc changed text --- to-be-continued.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/to-be-continued.md b/to-be-continued.md index 598616c..d1e90af 100644 --- a/to-be-continued.md +++ b/to-be-continued.md @@ -4,7 +4,7 @@ Congratulations. You made it this far. I hope you were following the tutorial step by step and not just skipping over the chapters :) -I have received good feedback so far so I decided to start writing a book. [Click here](http://artofphp.com/) if you want to learn more or stay updated with how the book is coming along. +I have received good feedback so far so I decided to start writing a book. [Click here](http://artofphp.com/) to learn more about that. But don't worry, I will also keep working on this tutorial. I was a bit lazy over the summer but now that it getting colder again I will have much more time to spend on the tutorial. From 206e2ab45bb35d1cf3238049232e30e139d257a0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Sep 2015 21:55:29 +0200 Subject: [PATCH 070/314] name on next line --- to-be-continued.md | 1 + 1 file changed, 1 insertion(+) diff --git a/to-be-continued.md b/to-be-continued.md index d1e90af..d485168 100644 --- a/to-be-continued.md +++ b/to-be-continued.md @@ -11,4 +11,5 @@ But don't worry, I will also keep working on this tutorial. I was a bit lazy ove If you got something out of the tutorial I would appreciate a star. It's the only way for me to see if people are actually reading this :) Thanks for your time, + Patrick \ No newline at end of file From 44c0bab26855e6f5587817474e02b3006544697e Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Sep 2015 21:55:58 +0200 Subject: [PATCH 071/314] spacing --- to-be-continued.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/to-be-continued.md b/to-be-continued.md index d485168..14a1d52 100644 --- a/to-be-continued.md +++ b/to-be-continued.md @@ -10,6 +10,8 @@ But don't worry, I will also keep working on this tutorial. I was a bit lazy ove If you got something out of the tutorial I would appreciate a star. It's the only way for me to see if people are actually reading this :) + + Thanks for your time, Patrick \ No newline at end of file From d1a3c011a4fcd82ddcef601967859f23897d65df Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 23 Sep 2015 20:23:35 +0200 Subject: [PATCH 072/314] fixed weird sentence --- 09-templating.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/09-templating.md b/09-templating.md index f023224..3e99998 100644 --- a/09-templating.md +++ b/09-templating.md @@ -16,7 +16,7 @@ You could just type hint against the concrete class. But the problem with this a In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want to add functionality to the engine. You can't do that without going back and changing all your code that is tightly coupled. -What we want is loose coupling. We will type hint against a class that implements an interface. So if you need another implementation, you just implement that interface in your new class and inject the new class instead. +What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need another implementation, you just implement that interface in your new class and inject the new class instead. Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). This sounds a lot more complicated than it is, so just follow along. From 72d542843d12b72a9844f29039bdfd18c342eaa9 Mon Sep 17 00:00:00 2001 From: Trevor Sawler Date: Thu, 24 Sep 2015 09:07:04 -0300 Subject: [PATCH 073/314] Correct namespace --- 07-inversion-of-control.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/07-inversion-of-control.md b/07-inversion-of-control.md index 2200da5..8a1f5dd 100644 --- a/07-inversion-of-control.md +++ b/07-inversion-of-control.md @@ -13,7 +13,7 @@ Change your `Homepage` controller to the following: ```php Date: Wed, 11 Nov 2015 15:35:46 -0500 Subject: [PATCH 074/314] #35 slightly more logical grouping --- 08-dependency-injector.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/08-dependency-injector.md b/08-dependency-injector.md index d2dd08e..bb0bc83 100644 --- a/08-dependency-injector.md +++ b/08-dependency-injector.md @@ -13,7 +13,7 @@ Install the Auryn package and then create a new file called `Dependencies.php` i $injector = new \Auryn\Injector; -$injector->alias('Http\Response', 'Http\HttpResponse'); +$injector->alias('Http\Request', 'Http\HttpRequest'); $injector->share('Http\HttpRequest'); $injector->define('Http\HttpRequest', [ ':get' => $_GET, @@ -23,7 +23,7 @@ $injector->define('Http\HttpRequest', [ ':server' => $_SERVER, ]); -$injector->alias('Http\Request', 'Http\HttpRequest'); +$injector->alias('Http\Response', 'Http\HttpResponse'); $injector->share('Http\HttpResponse'); return $injector; From e81e39c8acd7b8a3ec7dfaaa4532f808dc04528d Mon Sep 17 00:00:00 2001 From: Hassan Althaf Date: Thu, 26 Nov 2015 11:51:22 +0530 Subject: [PATCH 075/314] Fixed an issue. Fixed the issue stated in: https://github.com/PatrickLouys/no-framework-tutorial/issues/38 --- 09-templating.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/09-templating.md b/09-templating.md index 3e99998..a679aaa 100644 --- a/09-templating.md +++ b/09-templating.md @@ -134,11 +134,7 @@ In your project root folder, create a `templates` folder. In there, create a fil Hello {{ name }} ``` -Now you can go back to your `Homepage` controller and change the render line to `$content = $this->renderer - - - -->render('Homepage', $data);` +Now you can go back to your `Homepage` controller and change the render line to `$html = $this->renderer->render('Homepage', $data);` Navigate to the hello page in your browser to make sure everything works. And as always, don't forget to commit your changes. From 518e8687eb529cd8709cd9f0d55e9246694278e9 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 29 Nov 2015 19:44:09 +0900 Subject: [PATCH 076/314] Fix code highlight --- 10-dynamic-pages.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index 8565eb7..e2171e8 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -179,7 +179,7 @@ class InvalidPageException extends Exception Then in the `FilePageReader` file add this code at the end of your `readBySlug` method: -``` +```php $path = "$this->pageFolder/$slug.md"; if(!file_exists($path)) { @@ -223,4 +223,4 @@ Try a few different URLs to check that everything is working as it should. If so And as always, don't forget to commit your changes. -[<< previous](09-templating.md) | [next >>](11-page-menu.md) \ No newline at end of file +[<< previous](09-templating.md) | [next >>](11-page-menu.md) From 2e8656576668dbedec67747898ac384c6358622f Mon Sep 17 00:00:00 2001 From: Steven Orr Date: Sat, 12 Dec 2015 22:54:35 -0800 Subject: [PATCH 077/314] Replace word 'order' with 'folder'. Author intended to instruct the creation of a new folder called 'Menu'. --- 11-page-menu.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/11-page-menu.md b/11-page-menu.md index c46782b..befdc27 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -181,7 +181,7 @@ Everything should work now, but it doesn't really make sense that the menu is de Right now the menu is defined in the array, but it is very likely that this will change in the future. Maybe you want to define it in the database or maybe you even want to generate it dynamically based on the pages available. We don't have this information and things might change in the future. -So let's do the right thing here and start with an interface again. But first, create a new order in the `src` directory for the menu related things. `Menu` sounds like a reasonable name, doesn't it? +So let's do the right thing here and start with an interface again. But first, create a new folder in the `src` directory for the menu related things. `Menu` sounds like a reasonable name, doesn't it? ```php >](12-frontend.md) \ No newline at end of file +[<< previous](10-dynamic-pages.md) | [next >>](12-frontend.md) From 939914d973e204f0f36de5648d5e9cdc70f9e978 Mon Sep 17 00:00:00 2001 From: Steven Orr Date: Sat, 12 Dec 2015 23:37:09 -0800 Subject: [PATCH 078/314] Replace 'FileMenuReader' with correct reader. Author intended on sharing 'ArrayMenuReader' not unknown 'FileMenuReader' with injector. --- 11-page-menu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/11-page-menu.md b/11-page-menu.md index befdc27..3e793e3 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -220,7 +220,7 @@ Add these lines above the `return` statement: ```php $injector->alias('Example\Menu\MenuReader', 'Example\Menu\ArrayMenuReader'); -$injector->share('Example\Menu\FileMenuReader'); +$injector->share('Example\Menu\ArrayMenuReader'); ``` Now you need to change out the hardcoded array in the `FrontendTwigRenderer` class to make it use our new `MenuReader` instead. Give it a try without looking at the solution below. From 477489988b95e4d37646a88ffd76749112c3d798 Mon Sep 17 00:00:00 2001 From: Danack Date: Thu, 17 Dec 2015 02:29:43 +0000 Subject: [PATCH 079/314] Changed lines that said not to commit the lock file. Because not committing it is a bad idea. --- 02-composer.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/02-composer.md b/02-composer.md index fca4f45..47ff793 100644 --- a/02-composer.md +++ b/02-composer.md @@ -40,14 +40,9 @@ In the autoload part you can see that I am using the `Example` namespace for the Open a new console window and navigate into your project root folder. There run `composer update`. -Composer creates a `composer.lock` file that locks in your dependencies and a vendor directory. To remove those from your Git repository, add a new file in your project root folder called `.gitignore` with the following content: +Composer creates a `composer.lock` file that locks in your dependencies and a vendor directory. -```php -composer.lock -vendor/ -``` - -This will exclude the included file and folder from your commits. For which now would be a good time, by the way. +Committing the `composer.lock` file into version control is generally good practice for projects. It allows continuation testing tools (such as [Travis CI](https://travis-ci.org/)) to run the tests against the exact same versions of libraries that you're developing against. It also allows all people who are working on the project to use the exact same version of libraries i.e. it eliminates a source of "works on my machine" problems. Now you have successfully created an empty playground which you can use to set up your project. From 8683cba06557291428cdcea43ec05968a5fb5988 Mon Sep 17 00:00:00 2001 From: Gourab Nag Date: Sun, 3 Apr 2016 02:18:02 +0530 Subject: [PATCH 080/314] Fixed Typo in line 38 --- 07-inversion-of-control.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/07-inversion-of-control.md b/07-inversion-of-control.md index 8a1f5dd..5a13293 100644 --- a/07-inversion-of-control.md +++ b/07-inversion-of-control.md @@ -35,7 +35,7 @@ class Homepage Note that you are [importing](http://php.net/manual/en/language.namespaces.importing.php) `Http\Response` at the top of the file. This means that whenever you use `Response` inside this file, it will resolve to the fully qualified name. -In the contructor you are now explicitly asking for a `Http\Response`. In this case, `Http\Response` is an interface. So any class that implements the interface can be injected. See [type hinting](http://php.net/manual/en/language.oop5.typehinting.php) and [interfaces](http://php.net/manual/en/language.oop5.interfaces.php) for reference. +In the constructor you are now explicitly asking for a `Http\Response`. In this case, `Http\Response` is an interface. So any class that implements the interface can be injected. See [type hinting](http://php.net/manual/en/language.oop5.typehinting.php) and [interfaces](http://php.net/manual/en/language.oop5.interfaces.php) for reference. Now the code will result in an error because you are not actually injecting anything. So let's fix that in your `Bootstrap.php` where you dispatch when a route was found: @@ -48,4 +48,4 @@ The `Http\HttpResponse` object implements the `Http\Response` interface, so it f Now everything should work again. But if you follow this example, all your objects that are instantiated this way will have the same objects injected. This is of course not good, so let's fix that in the next part. -[<< previous](06-dispatching-to-a-class.md) | [next >>](08-dependency-injector.md) \ No newline at end of file +[<< previous](06-dispatching-to-a-class.md) | [next >>](08-dependency-injector.md) From c9df0411b489c968e893e58cb0adb04a63b94cdc Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Wed, 31 Aug 2016 12:51:41 +0200 Subject: [PATCH 081/314] Create LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..edff22d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Patrick Louys + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From bff19180302350fa371f3dceaffc1be8d8592386 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 15:21:21 +0100 Subject: [PATCH 082/314] Updated PHP version requirement --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 49738f9..793f038 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ I saw a lot of people coming into the Stack Overflow PHP chatroom and asking if So my goal with this is to provide an easy resource that people can be pointed to. In most cases a framework does not make sense and writing an application from scratch with the help of some third party packages is much, much easier than some people think. -**This tutorial was written for PHP 5.5 or newer versions.** If you are using an older version, please upgrade it before you start. I recommend that you use the [current stable version](http://php.net/downloads.php). +**This tutorial was written for PHP 7.0 or newer versions.** If you are using an older version, please upgrade it before you start. I recommend that you use the [current stable version](http://php.net/downloads.php). So let's get started right away with the [first part](01-front-controller.md). From f9a3ccd4c35eca90b3734f89e7868f0a438469e4 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 15:33:18 +0100 Subject: [PATCH 083/314] Added strict mode --- 01-front-controller.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/01-front-controller.md b/01-front-controller.md index 75315ea..3032f61 100644 --- a/01-front-controller.md +++ b/01-front-controller.md @@ -17,13 +17,15 @@ So instead of doing that, create a folder in your project folder called `public` Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: ```php - Date: Tue, 1 Nov 2016 15:35:51 +0100 Subject: [PATCH 084/314] Updated PHP version --- 02-composer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/02-composer.md b/02-composer.md index 47ff793..0dd1b3e 100644 --- a/02-composer.md +++ b/02-composer.md @@ -26,7 +26,7 @@ Add the following content to the file: } ], "require": { - "php": ">=5.5.0" + "php": ">=7.0.0" }, "autoload": { "psr-4": { From 625bf8ff1f7daea3fabe3bbc93c8ac81a0c16b3e Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 15:40:10 +0100 Subject: [PATCH 085/314] Added gitignore --- 01-front-controller.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/01-front-controller.md b/01-front-controller.md index 3032f61..0006f1e 100644 --- a/01-front-controller.md +++ b/01-front-controller.md @@ -44,4 +44,10 @@ If there is an error, go back and try to fix it. If you only see a blank page, c Now would be a good time to commit your progress. If you are not already using Git, set up a repository now. This is not a Git tutorial so I won't go over the details. But using version control should be a habit, even if it is just for a tutorial project like this. +Some editors and IDE's put their own files into your project folders. If that is the case, create a `.gitignore` file in your project root and exclude the files/directories. Below is an example for PHPStorm: + +``` +.idea/ +``` + [next >>](02-composer.md) From 4a9f46a50346db5611d1aa5175f17a85858efbeb Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 15:44:31 +0100 Subject: [PATCH 086/314] Adding vendor folder to gitignore --- 02-composer.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/02-composer.md b/02-composer.md index 0dd1b3e..1771dec 100644 --- a/02-composer.md +++ b/02-composer.md @@ -44,6 +44,12 @@ Composer creates a `composer.lock` file that locks in your dependencies and a ve Committing the `composer.lock` file into version control is generally good practice for projects. It allows continuation testing tools (such as [Travis CI](https://travis-ci.org/)) to run the tests against the exact same versions of libraries that you're developing against. It also allows all people who are working on the project to use the exact same version of libraries i.e. it eliminates a source of "works on my machine" problems. +That being said, [you don't want to put the actual source code of your dependencies in your git repository](https://getcomposer.org/doc/faqs/should-i-commit-the-dependencies-in-my-vendor-directory.md). So let's add a rule to our `.gitignore` file: + +``` +vendor/ +``` + Now you have successfully created an empty playground which you can use to set up your project. [<< previous](01-front-controller.md) | [next >>](03-error-handler.md) From 86af38af204328a8d4611e1e74159246093b166d Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 15:49:27 +0100 Subject: [PATCH 087/314] Updated composer require --- 03-error-handler.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/03-error-handler.md b/03-error-handler.md index 401762c..4429df6 100644 --- a/03-error-handler.md +++ b/03-error-handler.md @@ -14,8 +14,8 @@ To install a new package, open up your `composer.json` and add the package to th ```php "require": { - "php": ">=5.5.0", - "filp/whoops": ">=1.1.2" + "php": ">=7.0.0", + "filp/whoops": "~2.1" }, ``` From 2c59ba04da5fa5678490b3e3083a8b780fac0f37 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 15:52:51 +0100 Subject: [PATCH 088/314] Updated code example --- 03-error-handler.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/03-error-handler.md b/03-error-handler.md index 4429df6..b045e70 100644 --- a/03-error-handler.md +++ b/03-error-handler.md @@ -30,7 +30,7 @@ For development that does not make sense though -- you want a nice error page. T Then after the error handler registration, throw an `Exception` to test if everything is working correctly. Your `Bootstrap.php` should now look similar to this: ```php -pushHandler(new \Whoops\Handler\PrettyPageHandler); } else { $whoops->pushHandler(function($e){ - echo 'Friendly error page and send an email to the developer'; + echo 'Todo: Friendly error page and send an email to the developer'; }); } $whoops->register(); From 973631449f19b9ef89ffd1cb8e15c9f86f302db4 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 15:55:40 +0100 Subject: [PATCH 089/314] Rewrote some sentences --- 04-http.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/04-http.md b/04-http.md index cc33fe3..b245ede 100644 --- a/04-http.md +++ b/04-http.md @@ -4,13 +4,13 @@ PHP already has a few things built in to make working with HTTP easier. For example there are the [superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. -These are good if you just want to get a small script up and running without much thought on maintenance. However, if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, then you will want a class with a nice object-oriented interface that you can use in your application. +These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, then you will want a class with a nice object-oriented interface that you can use in your application instead. Once again, you don't have to reinvent the wheel and just install a package. I decided to write my own [HTTP component](https://github.com/PatrickLouys/http) because I did not like the existing components, but you don't have to do the same. Some alternatives: [Symfony HttpFoundation](https://github.com/symfony/HttpFoundation), [Nette HTTP Component](https://github.com/nette/http), [Aura Web](https://github.com/auraphp/Aura.Web), [sabre/http](https://github.com/fruux/sabre-http) -In this tutorial I will use my own HTTP component, but of course you can use whichever package you like most. Just change the code accordingly. +In this tutorial I will use my own HTTP component, but of course you can use any package that you like. You just have to adapt the code from the tutorial yourself. Again, edit the `composer.json` to add the new component and then run `composer update`: From 0430b7d94caa190aa604c27e55f43fb16592aa97 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 15:59:17 +0100 Subject: [PATCH 090/314] Updated composer require --- 04-http.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/04-http.md b/04-http.md index b245ede..09abd12 100644 --- a/04-http.md +++ b/04-http.md @@ -15,11 +15,11 @@ In this tutorial I will use my own HTTP component, but of course you can use any Again, edit the `composer.json` to add the new component and then run `composer update`: ```json -"require": { - "php": ">=5.5.0", - "filp/whoops": ">=1.1.2", - "patricklouys/http": ">=1.1.0" -}, + "require": { + "php": ">=7.0.0", + "filp/whoops": "~2.1", + "patricklouys/http": "~1.4" + }, ``` Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): From 4169afd54251e787c03723ec34aa4e9356f4bcd1 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 16:01:35 +0100 Subject: [PATCH 091/314] Made code location more explicit --- 04-http.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-http.md b/04-http.md index 09abd12..53ed554 100644 --- a/04-http.md +++ b/04-http.md @@ -45,7 +45,7 @@ This will send the response data to the browser. If you don't do this, nothing h The second parameter of `header()` is false because otherwise existing headers will be overwritten. -Right now it is just sending an empty response back to the browser with the status code `200`; to change that, add the following code between the code snippets from above: +Right now it is just sending an empty response back to the browser with the status code `200`; to change that, add the following code between the code snippets from above (just on top of the `foreach` statement): ```php $content = '

Hello World

'; From b2d2fee01316ca8940e28e81309a52a313df4f4e Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 16:18:02 +0100 Subject: [PATCH 092/314] Update 05-router.md --- 05-router.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/05-router.md b/05-router.md index d635a58..fda5b22 100644 --- a/05-router.md +++ b/05-router.md @@ -12,7 +12,7 @@ Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Au By now you know how to install Composer packages, so I will leave that to you. -Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last part. +Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. ```php $dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { @@ -49,7 +49,7 @@ This setup might work for really small applications, but once you start adding a Create a `Routes.php` file in the `src/` folder. It should look like this: ```php ->](06-dispatching-to-a-class.md) From d9b7885c432088dd68f8591444d80bbe576d526f Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 16:23:28 +0100 Subject: [PATCH 093/314] Updated code --- 06-dispatching-to-a-class.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/06-dispatching-to-a-class.md b/06-dispatching-to-a-class.md index 3221f44..7df552a 100644 --- a/06-dispatching-to-a-class.md +++ b/06-dispatching-to-a-class.md @@ -11,7 +11,7 @@ We will need a descriptive name for the classes that handle the requests. For th Create a new folder inside the `src/` folder with the name `Controllers`.In this folder we will place all our controller classes. In there, create a `Homepage.php` file. ```php ->](07-inversion-of-control.md) \ No newline at end of file +[<< previous](05-router.md) | [next >>](07-inversion-of-control.md) From 31bbb0fff7533c2e16cefeb62d71f3406c037d4e Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 16:26:05 +0100 Subject: [PATCH 094/314] Improved sentences --- 07-inversion-of-control.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/07-inversion-of-control.md b/07-inversion-of-control.md index 5a13293..a5d82de 100644 --- a/07-inversion-of-control.md +++ b/07-inversion-of-control.md @@ -2,11 +2,11 @@ ### Inversion of Control -In the last part you have set up a controller class and generated output with `echo`. But let's not forget that you have a nice object oriented HTTP abstraction available. But right now it's not accessible inside your class. +In the last part you have set up a controller class and generated output with `echo`. But let's not forget that we have a nice object oriented HTTP abstraction available. But right now it's not accessible inside your class. The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). -If it sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is implemented things will make more sense. +If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is implemented, it will make sense. Change your `Homepage` controller to the following: @@ -33,11 +33,11 @@ class Homepage } ``` -Note that you are [importing](http://php.net/manual/en/language.namespaces.importing.php) `Http\Response` at the top of the file. This means that whenever you use `Response` inside this file, it will resolve to the fully qualified name. +Note that we are [importing](http://php.net/manual/en/language.namespaces.importing.php) `Http\Response` at the top of the file. This means that whenever you use `Response` inside this file, it will resolve to the fully qualified name. -In the constructor you are now explicitly asking for a `Http\Response`. In this case, `Http\Response` is an interface. So any class that implements the interface can be injected. See [type hinting](http://php.net/manual/en/language.oop5.typehinting.php) and [interfaces](http://php.net/manual/en/language.oop5.interfaces.php) for reference. +In the constructor we are now explicitly asking for a `Http\Response`. In this case, `Http\Response` is an interface. So any class that implements the interface can be injected. See [type hinting](http://php.net/manual/en/language.oop5.typehinting.php) and [interfaces](http://php.net/manual/en/language.oop5.interfaces.php) for reference. -Now the code will result in an error because you are not actually injecting anything. So let's fix that in your `Bootstrap.php` where you dispatch when a route was found: +Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: ```php $class = new $className($response); From d6a70e94c067334d91ae429cd53fd9b8fd16b578 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 16:27:27 +0100 Subject: [PATCH 095/314] Update 07-inversion-of-control.md --- 07-inversion-of-control.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/07-inversion-of-control.md b/07-inversion-of-control.md index a5d82de..6149cc4 100644 --- a/07-inversion-of-control.md +++ b/07-inversion-of-control.md @@ -11,7 +11,7 @@ If this sounds a little complicated right now, don't worry. Just follow the tuto Change your `Homepage` controller to the following: ```php - Date: Tue, 1 Nov 2016 16:29:36 +0100 Subject: [PATCH 096/314] Update 08-dependency-injector.md --- 08-dependency-injector.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/08-dependency-injector.md b/08-dependency-injector.md index bb0bc83..d7e583f 100644 --- a/08-dependency-injector.md +++ b/08-dependency-injector.md @@ -4,7 +4,7 @@ A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when the class is instantiated. -There is only one injector that I can recommend: [Auryn](https://github.com/rdlowrey/Auryn). Sadly all the alternatives that I am aware of are using the [service locator antipattern](http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/). +There is only one injector that I can recommend: [Auryn](https://github.com/rdlowrey/Auryn). Sadly all the alternatives that I am aware of are using the [service locator antipattern](http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/) in their documentation and examples. Install the Auryn package and then create a new file called `Dependencies.php` in your `src/` folder. In there add the following content: From 32a9e944509316212823cd781491535a950e8e77 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 16:30:51 +0100 Subject: [PATCH 097/314] added strict mode --- 08-dependency-injector.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/08-dependency-injector.md b/08-dependency-injector.md index d7e583f..4469392 100644 --- a/08-dependency-injector.md +++ b/08-dependency-injector.md @@ -9,7 +9,7 @@ There is only one injector that I can recommend: [Auryn](https://github.com/rdlo Install the Auryn package and then create a new file called `Dependencies.php` in your `src/` folder. In there add the following content: ```php - Date: Tue, 1 Nov 2016 16:48:27 +0100 Subject: [PATCH 098/314] Code changes --- 09-templating.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/09-templating.md b/09-templating.md index a679aaa..1f1bb7c 100644 --- a/09-templating.md +++ b/09-templating.md @@ -27,20 +27,20 @@ So what does our template engine actually need to do? For now we really just nee In there create a new interface `Renderer.php` that looks like this: ```php -engine = $engine; } - public function render($template, $data = []) + public function render($template, $data = []) : string { return $this->engine->render($template, $data); } @@ -71,7 +71,7 @@ Of course we also have to add a definition in our `Dependencies.php` file becaus Now in your `Homepage` controller, add the new dependency like this: ```php - Date: Tue, 1 Nov 2016 16:51:33 +0100 Subject: [PATCH 099/314] Update 09-templating.md --- 09-templating.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/09-templating.md b/09-templating.md index 1f1bb7c..08720de 100644 --- a/09-templating.md +++ b/09-templating.md @@ -6,7 +6,7 @@ A template engine is not necessary with PHP because the language itself can take A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. -For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install that package before you continue. +For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install that package before you continue (`composer require mustache/mustache`). Another well known alternative would be [Twig](http://twig.sensiolabs.org/). From ff9595d961d47cdd43155e4d764ae3dacb532d8e Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 16:56:57 +0100 Subject: [PATCH 100/314] Update 10-dynamic-pages.md --- 10-dynamic-pages.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index e2171e8..348e4e3 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -9,7 +9,7 @@ Our first feature will be dynamic pages generated from [markdown](http://en.wiki Create a `Page` controller with the following content: ```php - Date: Tue, 1 Nov 2016 17:00:27 +0100 Subject: [PATCH 101/314] Update 10-dynamic-pages.md --- 10-dynamic-pages.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index 348e4e3..5983b4c 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -42,20 +42,20 @@ So let's put that functionality into a separate class. There is a good chance th In your 'src' folder, create a new folder `Page`. In there we will put all the page related classes. Add a new file in there called `PageReader.php` with this content: ```php -pageFolder = $pageFolder; } - public function readBySlug($slug) + public function readBySlug($slug) : string { return 'I am a placeholder'; } @@ -119,7 +119,7 @@ Did you get everything to work? If not, this is how the beginning of your controller should look now: ```php - Date: Tue, 1 Nov 2016 17:14:02 +0100 Subject: [PATCH 102/314] Update 10-dynamic-pages.md --- 10-dynamic-pages.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index 5983b4c..ca41bb5 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -65,11 +65,8 @@ class FilePageReader implements PageReader { private $pageFolder; - public function __construct($pageFolder) + public function __construct(string $pageFolder) { - if (!is_string($pageFolder)) { - throw new InvalidArgumentException('pageFolder must be a string'); - } $this->pageFolder = $pageFolder; } @@ -84,8 +81,6 @@ As you can see we are requiring the page folder path as a constructor argument. You could also put the page related things into it's own package and reuse it in different applications. Because we are not tightly coupling things, things are very flexible. -Because PHP does not have the ability to type hint for scalar values (things like strings and integers), we have to manually check that `$pageFolder` is a string. If we don't do that, there might be a bug in the future that is hard to find if a wrong type is injected. By throwing an exception, this can be caught and debugged immediately. - This will do for now. Let's create a template file for our pages with the name `Page.html` in the `templates` folder. For now just add `{{ content }}` in there. Add the following to your `Dependencies.php` file so that the application know which implementation to inject for our new interface. We also define the the `pageFolder` there. From 57f61183083cfe1ca06b50e8cd6c1f2149e243d4 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 17:17:52 +0100 Subject: [PATCH 103/314] Update 10-dynamic-pages.md --- 10-dynamic-pages.md | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index ca41bb5..eea4ac7 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -48,7 +48,7 @@ namespace Example\Page; interface PageReader { - public function readBySlug($slug) : string; + public function readBySlug(string $slug) : string; } ``` @@ -70,7 +70,7 @@ class FilePageReader implements PageReader $this->pageFolder = $pageFolder; } - public function readBySlug($slug) : string + public function readBySlug(string $slug) : string { return 'I am a placeholder'; } @@ -142,18 +142,7 @@ class Page So far so good, now let's make our `FilePageReader` actually do some work. -Again, let's check first that the proper type was passed into the method: - -```php -public function readBySlug($slug) -{ - if (!is_string($slug)) { - throw new InvalidArgumentException('slug must be a string'); - } -} -``` - -We also need to be able to communicate that a page was not found. For this we can create a custom exception that we can catch later. In your `src/Page` folder, create a `InvalidPageException.php` file with this content: +We need to be able to communicate that a page was not found. For this we can create a custom exception that we can catch later. In your `src/Page` folder, create a `InvalidPageException.php` file with this content: ```php Date: Tue, 1 Nov 2016 17:22:53 +0100 Subject: [PATCH 104/314] Update 10-dynamic-pages.md --- 10-dynamic-pages.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index eea4ac7..d611369 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -166,7 +166,7 @@ Then in the `FilePageReader` file add this code at the end of your `readBySlug` ```php $path = "$this->pageFolder/$slug.md"; -if(!file_exists($path)) { +if (!file_exists($path)) { throw new InvalidPageException($slug); } From c80b25a34c82381c060d307d81b81a983400983f Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 17:24:08 +0100 Subject: [PATCH 105/314] Update 10-dynamic-pages.md --- 10-dynamic-pages.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index d611369..398ba81 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -196,6 +196,8 @@ public function show($params) } ``` +Make sure that you use an `use` statement for the `InvalidPageException` at the top of the file. + Try a few different URLs to check that everything is working as it should. If something is wrong, go back and debug it until it works. And as always, don't forget to commit your changes. From 7a9f6d84ee0a4630543e2bc06e1eb293b78a9446 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 17:52:04 +0100 Subject: [PATCH 106/314] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 793f038..1713dc7 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,4 @@ So let's get started right away with the [first part](01-front-controller.md). 9. [Templating](09-templating.md) 10. [Dynamic Pages](10-dynamic-pages.md) 11. [Page Menu](11-page-menu.md) +12. [Frontend](12-frontend.md) From a864b2aad5bcafcf912761643ee7e3539d9741f9 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 17:52:22 +0100 Subject: [PATCH 107/314] Update 12-frontend.md --- 12-frontend.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/12-frontend.md b/12-frontend.md index 620d878..bd30564 100644 --- a/12-frontend.md +++ b/12-frontend.md @@ -5,7 +5,7 @@ I don't know about you, but I don't like to work on a site that looks two decades old. So let's improve the look of our little application. -This is not a frontend tutorial, so we'll just [pure](http://purecss.io/) and call it a day. +This is not a frontend tutorial, so we'll just use [pure](http://purecss.io/) and call it a day. First we need to change the `Layout.html` template. I don't want to waste your time with HTML and CSS, so I'll just provide the code for you to copy paste. From 9a7b58654815a2d3729e3b83ec7b99ed261fcaee Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Wed, 2 Nov 2016 13:03:57 +0100 Subject: [PATCH 108/314] Update 11-page-menu.md --- 11-page-menu.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/11-page-menu.md b/11-page-menu.md index 3e793e3..6490067 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -2,17 +2,17 @@ ### Page Menu -Now we have some sweet dynamic pages. But nobody can find them. +Now we have made a few nice dynamic pages. But nobody can find them. -Let's fix that now. In this chapter we will create a menu with links to all our pages. +Let's fix that now. In this chapter we will create a menu with links to all of our pages. -When we have a menu, we will want to be able to reuse the same code on multiple page. We could create a separate file and include it every time, but there is a better solution. +When we have a menu, we will want to be able to reuse the same code on multiple pages. We could create a separate file and include it every time, but there is a better solution. It is more practical to have templates that are able to extend other templates, like a layout for example. Then we can have all the layout related code in a single file and we don't have to include header and footer files in every template. -Sadly our implementation of mustache does not support this. We could write code to work around this, which will take time and could introduce some bugs. Or we could switch to a library that already supports this and is well tested. [Twig](http://twig.sensiolabs.org/) for example. +Our implementation of mustache does not support this. We could write code to work around this, which will take time and could introduce some bugs. Or we could switch to a library that already supports this and is well tested. [Twig](http://twig.sensiolabs.org/) for example. -Now you might wonder why we didn't start with Twig right away. This is a good example to show why using interfaces and writing loosely-coupled code is a good idea. +Now you might wonder why we didn't start with Twig right away. Because this is a good example to show why using interfaces and writing loosely-coupled code is a good idea. Like in the real world, the requirements suddenly changed and now our code needs to adapt. Remember how you created a `MustacheRenderer` in [chapter 9](09-templating.md)? This time, we create a `TwigRenderer` that implements the same interface. From 1e5cd00b037dcc48d38b4baf28c0f3f262d1af14 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Wed, 2 Nov 2016 13:05:02 +0100 Subject: [PATCH 109/314] Update 11-page-menu.md --- 11-page-menu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/11-page-menu.md b/11-page-menu.md index 6490067..6a14fd8 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -16,7 +16,7 @@ Now you might wonder why we didn't start with Twig right away. Because this is a Remember how you created a `MustacheRenderer` in [chapter 9](09-templating.md)? This time, we create a `TwigRenderer` that implements the same interface. -But before we start, install the latest version of Twig with composer. +But before we start, install the latest version of Twig with composer (`composer require "twig/twig:~1.0"`). Then create the a `TwigRenderer.php` in your `src/Template` folder that looks like this: From 6f915724343987afc75ed3d54a332fd0c7e587de Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Wed, 2 Nov 2016 13:07:37 +0100 Subject: [PATCH 110/314] Update 11-page-menu.md --- 11-page-menu.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/11-page-menu.md b/11-page-menu.md index 6a14fd8..8c0e4b5 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -21,7 +21,7 @@ But before we start, install the latest version of Twig with composer (`composer Then create the a `TwigRenderer.php` in your `src/Template` folder that looks like this: ```php -renderer = $renderer; } - public function render($template, $data = []) + public function render($template, $data = []) : string { return $this->renderer->render("$template.html", $data); } @@ -123,7 +123,7 @@ We could create a global variable that is usable by all templates, but that is n So instead we will use a custom renderer for the frontend. First we create an empty interface that extends the existing `Renderer` interface. ```php -renderer = $renderer; } - public function render($template, $data = []) + public function render($template, $data = []) : string { $data = array_merge($data, [ 'menuItems' => [['href' => '/', 'text' => 'Homepage']], @@ -184,26 +184,26 @@ Right now the menu is defined in the array, but it is very likely that this will So let's do the right thing here and start with an interface again. But first, create a new folder in the `src` directory for the menu related things. `Menu` sounds like a reasonable name, doesn't it? ```php - '/', 'text' => 'Homepage'], @@ -228,7 +228,7 @@ Now you need to change out the hardcoded array in the `FrontendTwigRenderer` cla Did you finish it or did you get stuck? Or are you just lazy? Doesn't matter, here is a working solution: ```php -menuReader = $menuReader; } - public function render($template, $data = []) + public function render($template, $data = []) : string { $data = array_merge($data, [ 'menuItems' => $this->menuReader->readMenu(), From dd0d96305fb79fa49c850b3b09016eefdd3b324e Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Wed, 2 Nov 2016 13:14:13 +0100 Subject: [PATCH 111/314] Update 11-page-menu.md --- 11-page-menu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/11-page-menu.md b/11-page-menu.md index 8c0e4b5..d45849e 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -48,7 +48,7 @@ As you can see, on the render function call a `.html` is added. This is because Add the following code to your `Dependencies.php` file: ```php -$injector->delegate('Twig_Environment', function() use ($injector) { +$injector->delegate('Twig_Environment', function () use ($injector) { $loader = new Twig_Loader_Filesystem(dirname(__DIR__) . '/templates'); $twig = new Twig_Environment($loader); return $twig; From 2f4c1f044d62829153c18a797a3465eea7ac23dd Mon Sep 17 00:00:00 2001 From: Michael Skvortsov Date: Thu, 2 Mar 2017 03:51:48 +0200 Subject: [PATCH 112/314] Update 04-http.md A typo fixed --- 04-http.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-http.md b/04-http.md index 53ed554..cf9226d 100644 --- a/04-http.md +++ b/04-http.md @@ -59,7 +59,7 @@ $response->setContent('404 - Page not found'); $response->setStatusCode(404); ``` -Remember that the object is only storing data, so you if you set multiple status codes before you send the response, only the last one will be applied. +Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. I will show you in later parts how to use the different features of the components. In the meantime, feel free to read the [documentation](https://github.com/PatrickLouys/http) or the source code if you want to find out how something works. From 4688caf85e63d6a99603f5679f58c92ffd1361af Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Fri, 8 Dec 2017 20:32:06 +0100 Subject: [PATCH 113/314] Update to-be-continued.md --- to-be-continued.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/to-be-continued.md b/to-be-continued.md index 14a1d52..7207c60 100644 --- a/to-be-continued.md +++ b/to-be-continued.md @@ -4,14 +4,12 @@ Congratulations. You made it this far. I hope you were following the tutorial step by step and not just skipping over the chapters :) -I have received good feedback so far so I decided to start writing a book. [Click here](http://artofphp.com/) to learn more about that. - -But don't worry, I will also keep working on this tutorial. I was a bit lazy over the summer but now that it getting colder again I will have much more time to spend on the tutorial. - If you got something out of the tutorial I would appreciate a star. It's the only way for me to see if people are actually reading this :) +Because this tutorial was so well-received, it inspired me to write a book. The book is a much more up to date version of this tutorial and covers a lot more. Click the link below to check it out (there is also a sample chapter available). +## [Professional PHP: Building maintainable and secure applications](http://patricklouys.com/professional-php/) Thanks for your time, -Patrick \ No newline at end of file +Patrick From 9652d05afed0454ddc163e8372d5d9c939165eda Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Fri, 8 Dec 2017 20:32:21 +0100 Subject: [PATCH 114/314] Update to-be-continued.md --- to-be-continued.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/to-be-continued.md b/to-be-continued.md index 7207c60..b745fa0 100644 --- a/to-be-continued.md +++ b/to-be-continued.md @@ -8,7 +8,7 @@ If you got something out of the tutorial I would appreciate a star. It's the onl Because this tutorial was so well-received, it inspired me to write a book. The book is a much more up to date version of this tutorial and covers a lot more. Click the link below to check it out (there is also a sample chapter available). -## [Professional PHP: Building maintainable and secure applications](http://patricklouys.com/professional-php/) +### [Professional PHP: Building maintainable and secure applications](http://patricklouys.com/professional-php/) Thanks for your time, From 24193f92abca9952539eda8bc0fe3bf08cd83a08 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Fri, 8 Dec 2017 20:36:52 +0100 Subject: [PATCH 115/314] Update to-be-continued.md --- to-be-continued.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/to-be-continued.md b/to-be-continued.md index b745fa0..a13a0fe 100644 --- a/to-be-continued.md +++ b/to-be-continued.md @@ -10,6 +10,8 @@ Because this tutorial was so well-received, it inspired me to write a book. The ### [Professional PHP: Building maintainable and secure applications](http://patricklouys.com/professional-php/) +![](http://patricklouys.com/img/professional-php-thumb.png) + Thanks for your time, Patrick From 8736e5cecb34270306cccac2f737c11ad96f18f6 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Fri, 8 Dec 2017 20:39:00 +0100 Subject: [PATCH 116/314] Update README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 1713dc7..ad2da80 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ +Because this tutorial was so well-received, it inspired me to write a book. The book is a much more up to date version of this tutorial and covers a lot more. Click the link below to check it out (there is also a sample chapter available). + +### [Professional PHP: Building maintainable and secure applications](http://patricklouys.com/professional-php/) + +![](http://patricklouys.com/img/professional-php-thumb.png) + +The tutorial is still available in it's original form below. + ## Create a PHP application without a framework ### Introduction From 3d561cd012accf65f5d6e4e0d9bf3d2e3ddccebe Mon Sep 17 00:00:00 2001 From: Stephen Moon Date: Thu, 9 Aug 2018 10:43:52 +0100 Subject: [PATCH 117/314] Small typo --- 05-router.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/05-router.md b/05-router.md index fda5b22..44c4ddb 100644 --- a/05-router.md +++ b/05-router.md @@ -42,7 +42,7 @@ switch ($routeInfo[0]) { } ``` -In the first part of the code, you are registering the available routes for you application. In the second part, the dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, the handler callable will be executed. +In the first part of the code, you are registering the available routes for your application. In the second part, the dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, the handler callable will be executed. This setup might work for really small applications, but once you start adding a few routes your bootstrap file will quickly get cluttered. So let's move them out into a separate file. From 48c9c9467d3c9b392413ac0d21374c4873fb0678 Mon Sep 17 00:00:00 2001 From: lubiana Date: Tue, 29 Mar 2022 20:35:06 +0200 Subject: [PATCH 118/314] add data from work folder --- .gitignore | 4 + 01-front-controller.md | 6 +- 02-composer.md | 66 +- 03-error-handler.md | 67 +- 04-development-helpers.md | 260 +++ 04-http.md | 66 - 05-http.md | 124 ++ 05-router.md | 81 - 06-dispatching-to-a-class.md | 56 - 06-router.md | 101 ++ 07-dispatching-to-a-class.md | 137 ++ 07-inversion-of-control.md | 51 - 08-dependency-injector.md | 97 - 08-inversion-of-control.md | 54 + 09-dependency-injector.md | 213 +++ 09-templating.md | 141 -- 10-dynamic-pages.md | 205 --- 10-invoker.md | 102 ++ 11-page-menu.md | 260 --- 11-templating.md | 240 +++ 12-configuration.md | 201 +++ 12-frontend.md | 188 -- 13-refactoring.md | 377 ++++ 14-middleware.md | 220 +++ README.md | 39 +- Vagrantfile | 22 + app/public/favicon.ico | Bin 0 -> 15086 bytes app/src/.gitkeep | 0 app/src/Http/AddRoute.php | 17 + app/src/Http/RouteDecorationMiddleware.php | 97 + app/src/Kernel.php | 63 + .../01-front-controller/public/index.php | 5 + .../01-front-controller/src/Bootstrap.php | 3 + implementation/02-composer/composer.json | 17 + implementation/02-composer/composer.lock | 202 +++ implementation/02-composer/public/index.php | 5 + implementation/02-composer/src/Bootstrap.php | 3 + implementation/03-error-handler/composer.json | 22 + implementation/03-error-handler/composer.lock | 202 +++ .../03-error-handler/public/index.php | 5 + .../03-error-handler/src/Bootstrap.php | 25 + .../04-dev-helpers/.php-cs-fixer.php | 41 + implementation/04-dev-helpers/composer.json | 29 + implementation/04-dev-helpers/composer.lock | 420 +++++ implementation/04-dev-helpers/phpstan.neon | 4 + .../04-dev-helpers/public/index.php | 5 + .../04-dev-helpers/src/Bootstrap.php | 30 + implementation/05-http/.php-cs-fixer.php | 41 + implementation/05-http/composer.json | 30 + implementation/05-http/composer.lock | 627 +++++++ implementation/05-http/phpstan-baseline.neon | 0 implementation/05-http/phpstan.neon | 4 + implementation/05-http/public/index.php | 5 + implementation/05-http/src/Bootstrap.php | 54 + implementation/06-router/.php-cs-fixer.php | 44 + implementation/06-router/composer.json | 31 + implementation/06-router/composer.lock | 677 +++++++ implementation/06-router/config/routes.php | 17 + .../06-router/phpstan-baseline.neon | 0 implementation/06-router/phpstan.neon | 4 + implementation/06-router/public/index.php | 5 + implementation/06-router/src/Bootstrap.php | 89 + .../07-dispatching-to-class/.php-cs-fixer.php | 44 + .../07-dispatching-to-class/composer.json | 32 + .../07-dispatching-to-class/composer.lock | 734 ++++++++ .../07-dispatching-to-class/config/routes.php | 8 + .../phpstan-baseline.neon | 0 .../07-dispatching-to-class/phpstan.neon | 4 + .../07-dispatching-to-class/public/index.php | 5 + .../src/Action/Another.php | 20 + .../src/Action/Hello.php | 21 + .../src/Action/InternalServerError.php | 20 + .../src/Action/NotAllowed.php | 20 + .../src/Action/NotFound.php | 20 + .../07-dispatching-to-class/src/Bootstrap.php | 102 ++ .../InternalServerErrorException.php | 11 + .../src/Exception/NotAllowedException.php | 11 + .../src/Exception/NotFoundException.php | 11 + .../08-inversion-of-control/.php-cs-fixer.php | 44 + .../08-inversion-of-control/composer.json | 32 + .../08-inversion-of-control/composer.lock | 734 ++++++++ .../08-inversion-of-control/config/routes.php | 8 + .../phpstan-baseline.neon | 0 .../08-inversion-of-control/phpstan.neon | 4 + .../08-inversion-of-control/public/index.php | 5 + .../src/Action/Action.php | 17 + .../src/Action/Another.php | 19 + .../src/Action/Hello.php | 20 + .../src/Action/InternalServerError.php | 19 + .../src/Action/NotAllowed.php | 19 + .../src/Action/NotFound.php | 19 + .../08-inversion-of-control/src/Bootstrap.php | 102 ++ .../InternalServerErrorException.php | 11 + .../src/Exception/NotAllowedException.php | 11 + .../src/Exception/NotFoundException.php | 11 + .../09-dependency-injector/.php-cs-fixer.php | 44 + .../09-dependency-injector/composer.json | 34 + .../09-dependency-injector/composer.lock | 1020 +++++++++++ .../config/dependencies.php | 11 + .../09-dependency-injector/config/routes.php | 8 + .../phpstan-baseline.neon | 7 + .../09-dependency-injector/phpstan.neon | 7 + .../09-dependency-injector/public/index.php | 5 + .../src/Action/Action.php | 18 + .../src/Action/Another.php | 19 + .../src/Action/Hello.php | 20 + .../src/Action/InternalServerError.php | 19 + .../src/Action/NotAllowed.php | 19 + .../src/Action/NotFound.php | 19 + .../09-dependency-injector/src/Bootstrap.php | 106 ++ .../InternalServerErrorException.php | 11 + .../src/Exception/NotAllowedException.php | 11 + .../src/Exception/NotFoundException.php | 11 + implementation/10-invoker/.php-cs-fixer.php | 47 + implementation/10-invoker/composer.json | 32 + implementation/10-invoker/composer.lock | 1020 +++++++++++ .../10-invoker/config/dependencies.php | 12 + implementation/10-invoker/config/routes.php | 18 + implementation/10-invoker/phpstan.neon | 5 + implementation/10-invoker/public/favicon.ico | Bin 0 -> 15086 bytes implementation/10-invoker/public/index.php | 6 + implementation/10-invoker/src/.gitkeep | 0 .../10-invoker/src/Action/Hello.php | 23 + .../10-invoker/src/Action/Other.php | 20 + implementation/10-invoker/src/Bootstrap.php | 110 ++ .../src/Exception/InternalServerError.php | 11 + .../src/Exception/MethodNotAllowed.php | 11 + .../10-invoker/src/Exception/NotFound.php | 11 + .../10-invoker/src/Service/Time/Now.php | 12 + .../src/Service/Time/SystemClockNow.php | 15 + .../11-templating/.php-cs-fixer.php | 38 + implementation/11-templating/.phpcs.xml.dist | 9 + implementation/11-templating/composer.json | 46 + implementation/11-templating/composer.lock | 1550 ++++++++++++++++ .../11-templating/config/dependencies.php | 32 + .../11-templating/config/routes.php | 12 + .../11-templating/config/settings.php | 9 + .../11-templating/phpstan-baseline.neon | 7 + implementation/11-templating/phpstan.neon | 8 + .../11-templating/public/favicon.ico | Bin 0 -> 15086 bytes implementation/11-templating/public/index.php | 5 + implementation/11-templating/src/.gitkeep | 0 .../11-templating/src/Action/Hello.php | 31 + .../11-templating/src/Action/Other.php | 19 + .../11-templating/src/Bootstrap.php | 110 ++ .../src/Exception/InternalServerError.php | 9 + .../src/Exception/MethodNotAllowed.php | 9 + .../11-templating/src/Exception/NotFound.php | 9 + .../11-templating/src/Service/Time/Now.php | 10 + .../src/Service/Time/SystemClockNow.php | 13 + implementation/11-templating/src/Settings.php | 13 + .../src/Template/MustacheRenderer.php | 17 + .../11-templating/src/Template/Renderer.php | 11 + .../11-templating/templates/hello.html | 11 + .../12-configuration/.php-cs-fixer.php | 38 + .../12-configuration/.phpcs.xml.dist | 9 + implementation/12-configuration/composer.json | 46 + implementation/12-configuration/composer.lock | 1550 ++++++++++++++++ .../12-configuration/config/dependencies.php | 22 + .../12-configuration/config/routes.php | 12 + .../12-configuration/config/settings.php | 10 + .../12-configuration/phpstan-baseline.neon | 0 implementation/12-configuration/phpstan.neon | 8 + .../12-configuration/public/favicon.ico | Bin 0 -> 15086 bytes .../12-configuration/public/index.php | 5 + implementation/12-configuration/src/.gitkeep | 0 .../12-configuration/src/Action/Hello.php | 31 + .../12-configuration/src/Action/Other.php | 19 + .../12-configuration/src/Bootstrap.php | 111 ++ .../src/Exception/InternalServerError.php | 9 + .../src/Exception/MethodNotAllowed.php | 9 + .../src/Exception/NotFound.php | 9 + .../src/Factory/ContainerProvider.php | 10 + .../Factory/FileSystemSettingsProvider.php | 18 + .../src/Factory/SettingsContainerProvider.php | 25 + .../src/Factory/SettingsProvider.php | 10 + .../12-configuration/src/Service/Time/Now.php | 10 + .../src/Service/Time/SystemClockNow.php | 13 + .../12-configuration/src/Settings.php | 14 + .../src/Template/MustacheRenderer.php | 17 + .../src/Template/Renderer.php | 11 + .../12-configuration/templates/hello.html | 11 + .../13-refactoring/.php-cs-fixer.php | 38 + implementation/13-refactoring/.phpcs.xml.dist | 9 + implementation/13-refactoring/composer.json | 47 + implementation/13-refactoring/composer.lock | 1607 +++++++++++++++++ .../13-refactoring/config/dependencies.php | 39 + .../13-refactoring/config/routes.php | 12 + .../13-refactoring/config/settings.php | 10 + .../13-refactoring/phpstan-baseline.neon | 7 + implementation/13-refactoring/phpstan.neon | 8 + .../13-refactoring/public/favicon.ico | Bin 0 -> 15086 bytes .../13-refactoring/public/index.php | 5 + implementation/13-refactoring/src/.gitkeep | 0 .../13-refactoring/src/Action/Hello.php | 31 + .../13-refactoring/src/Action/Other.php | 19 + .../13-refactoring/src/Bootstrap.php | 40 + .../src/Exception/InternalServerError.php | 9 + .../src/Exception/MethodNotAllowed.php | 9 + .../13-refactoring/src/Exception/NotFound.php | 9 + .../src/Factory/ContainerProvider.php | 10 + .../src/Factory/DiactorosRequestFactory.php | 23 + .../Factory/FileSystemSettingsProvider.php | 18 + .../src/Factory/RequestFactory.php | 11 + .../src/Factory/SettingsContainerProvider.php | 25 + .../src/Factory/SettingsProvider.php | 10 + .../13-refactoring/src/Http/BasicEmitter.php | 38 + .../13-refactoring/src/Http/Emitter.php | 10 + .../src/Http/InvokerRoutedHandler.php | 37 + .../src/Http/RouteMiddleware.php | 69 + .../src/Http/RoutedRequestHandler.php | 10 + implementation/13-refactoring/src/Kernel.php | 34 + .../13-refactoring/src/Service/Time/Now.php | 10 + .../src/Service/Time/SystemClockNow.php | 13 + .../13-refactoring/src/Settings.php | 14 + .../src/Template/MustacheRenderer.php | 17 + .../13-refactoring/src/Template/Renderer.php | 11 + .../13-refactoring/templates/hello.html | 11 + 218 files changed, 16123 insertions(+), 1233 deletions(-) create mode 100644 .gitignore create mode 100644 04-development-helpers.md delete mode 100644 04-http.md create mode 100644 05-http.md delete mode 100644 05-router.md delete mode 100644 06-dispatching-to-a-class.md create mode 100644 06-router.md create mode 100644 07-dispatching-to-a-class.md delete mode 100644 07-inversion-of-control.md delete mode 100644 08-dependency-injector.md create mode 100644 08-inversion-of-control.md create mode 100644 09-dependency-injector.md delete mode 100644 09-templating.md delete mode 100644 10-dynamic-pages.md create mode 100644 10-invoker.md delete mode 100644 11-page-menu.md create mode 100644 11-templating.md create mode 100644 12-configuration.md delete mode 100644 12-frontend.md create mode 100644 13-refactoring.md create mode 100644 14-middleware.md create mode 100644 Vagrantfile create mode 100644 app/public/favicon.ico create mode 100644 app/src/.gitkeep create mode 100644 app/src/Http/AddRoute.php create mode 100644 app/src/Http/RouteDecorationMiddleware.php create mode 100644 app/src/Kernel.php create mode 100644 implementation/01-front-controller/public/index.php create mode 100644 implementation/01-front-controller/src/Bootstrap.php create mode 100644 implementation/02-composer/composer.json create mode 100644 implementation/02-composer/composer.lock create mode 100644 implementation/02-composer/public/index.php create mode 100644 implementation/02-composer/src/Bootstrap.php create mode 100644 implementation/03-error-handler/composer.json create mode 100644 implementation/03-error-handler/composer.lock create mode 100644 implementation/03-error-handler/public/index.php create mode 100644 implementation/03-error-handler/src/Bootstrap.php create mode 100644 implementation/04-dev-helpers/.php-cs-fixer.php create mode 100644 implementation/04-dev-helpers/composer.json create mode 100644 implementation/04-dev-helpers/composer.lock create mode 100644 implementation/04-dev-helpers/phpstan.neon create mode 100644 implementation/04-dev-helpers/public/index.php create mode 100644 implementation/04-dev-helpers/src/Bootstrap.php create mode 100644 implementation/05-http/.php-cs-fixer.php create mode 100644 implementation/05-http/composer.json create mode 100644 implementation/05-http/composer.lock create mode 100644 implementation/05-http/phpstan-baseline.neon create mode 100644 implementation/05-http/phpstan.neon create mode 100644 implementation/05-http/public/index.php create mode 100644 implementation/05-http/src/Bootstrap.php create mode 100644 implementation/06-router/.php-cs-fixer.php create mode 100644 implementation/06-router/composer.json create mode 100644 implementation/06-router/composer.lock create mode 100644 implementation/06-router/config/routes.php create mode 100644 implementation/06-router/phpstan-baseline.neon create mode 100644 implementation/06-router/phpstan.neon create mode 100644 implementation/06-router/public/index.php create mode 100644 implementation/06-router/src/Bootstrap.php create mode 100644 implementation/07-dispatching-to-class/.php-cs-fixer.php create mode 100644 implementation/07-dispatching-to-class/composer.json create mode 100644 implementation/07-dispatching-to-class/composer.lock create mode 100644 implementation/07-dispatching-to-class/config/routes.php create mode 100644 implementation/07-dispatching-to-class/phpstan-baseline.neon create mode 100644 implementation/07-dispatching-to-class/phpstan.neon create mode 100644 implementation/07-dispatching-to-class/public/index.php create mode 100644 implementation/07-dispatching-to-class/src/Action/Another.php create mode 100644 implementation/07-dispatching-to-class/src/Action/Hello.php create mode 100644 implementation/07-dispatching-to-class/src/Action/InternalServerError.php create mode 100644 implementation/07-dispatching-to-class/src/Action/NotAllowed.php create mode 100644 implementation/07-dispatching-to-class/src/Action/NotFound.php create mode 100644 implementation/07-dispatching-to-class/src/Bootstrap.php create mode 100644 implementation/07-dispatching-to-class/src/Exception/InternalServerErrorException.php create mode 100644 implementation/07-dispatching-to-class/src/Exception/NotAllowedException.php create mode 100644 implementation/07-dispatching-to-class/src/Exception/NotFoundException.php create mode 100644 implementation/08-inversion-of-control/.php-cs-fixer.php create mode 100644 implementation/08-inversion-of-control/composer.json create mode 100644 implementation/08-inversion-of-control/composer.lock create mode 100644 implementation/08-inversion-of-control/config/routes.php create mode 100644 implementation/08-inversion-of-control/phpstan-baseline.neon create mode 100644 implementation/08-inversion-of-control/phpstan.neon create mode 100644 implementation/08-inversion-of-control/public/index.php create mode 100644 implementation/08-inversion-of-control/src/Action/Action.php create mode 100644 implementation/08-inversion-of-control/src/Action/Another.php create mode 100644 implementation/08-inversion-of-control/src/Action/Hello.php create mode 100644 implementation/08-inversion-of-control/src/Action/InternalServerError.php create mode 100644 implementation/08-inversion-of-control/src/Action/NotAllowed.php create mode 100644 implementation/08-inversion-of-control/src/Action/NotFound.php create mode 100644 implementation/08-inversion-of-control/src/Bootstrap.php create mode 100644 implementation/08-inversion-of-control/src/Exception/InternalServerErrorException.php create mode 100644 implementation/08-inversion-of-control/src/Exception/NotAllowedException.php create mode 100644 implementation/08-inversion-of-control/src/Exception/NotFoundException.php create mode 100644 implementation/09-dependency-injector/.php-cs-fixer.php create mode 100644 implementation/09-dependency-injector/composer.json create mode 100644 implementation/09-dependency-injector/composer.lock create mode 100644 implementation/09-dependency-injector/config/dependencies.php create mode 100644 implementation/09-dependency-injector/config/routes.php create mode 100644 implementation/09-dependency-injector/phpstan-baseline.neon create mode 100644 implementation/09-dependency-injector/phpstan.neon create mode 100644 implementation/09-dependency-injector/public/index.php create mode 100644 implementation/09-dependency-injector/src/Action/Action.php create mode 100644 implementation/09-dependency-injector/src/Action/Another.php create mode 100644 implementation/09-dependency-injector/src/Action/Hello.php create mode 100644 implementation/09-dependency-injector/src/Action/InternalServerError.php create mode 100644 implementation/09-dependency-injector/src/Action/NotAllowed.php create mode 100644 implementation/09-dependency-injector/src/Action/NotFound.php create mode 100644 implementation/09-dependency-injector/src/Bootstrap.php create mode 100644 implementation/09-dependency-injector/src/Exception/InternalServerErrorException.php create mode 100644 implementation/09-dependency-injector/src/Exception/NotAllowedException.php create mode 100644 implementation/09-dependency-injector/src/Exception/NotFoundException.php create mode 100644 implementation/10-invoker/.php-cs-fixer.php create mode 100644 implementation/10-invoker/composer.json create mode 100644 implementation/10-invoker/composer.lock create mode 100644 implementation/10-invoker/config/dependencies.php create mode 100644 implementation/10-invoker/config/routes.php create mode 100644 implementation/10-invoker/phpstan.neon create mode 100644 implementation/10-invoker/public/favicon.ico create mode 100644 implementation/10-invoker/public/index.php create mode 100644 implementation/10-invoker/src/.gitkeep create mode 100644 implementation/10-invoker/src/Action/Hello.php create mode 100644 implementation/10-invoker/src/Action/Other.php create mode 100644 implementation/10-invoker/src/Bootstrap.php create mode 100644 implementation/10-invoker/src/Exception/InternalServerError.php create mode 100644 implementation/10-invoker/src/Exception/MethodNotAllowed.php create mode 100644 implementation/10-invoker/src/Exception/NotFound.php create mode 100644 implementation/10-invoker/src/Service/Time/Now.php create mode 100644 implementation/10-invoker/src/Service/Time/SystemClockNow.php create mode 100644 implementation/11-templating/.php-cs-fixer.php create mode 100644 implementation/11-templating/.phpcs.xml.dist create mode 100644 implementation/11-templating/composer.json create mode 100644 implementation/11-templating/composer.lock create mode 100644 implementation/11-templating/config/dependencies.php create mode 100644 implementation/11-templating/config/routes.php create mode 100644 implementation/11-templating/config/settings.php create mode 100644 implementation/11-templating/phpstan-baseline.neon create mode 100644 implementation/11-templating/phpstan.neon create mode 100644 implementation/11-templating/public/favicon.ico create mode 100644 implementation/11-templating/public/index.php create mode 100644 implementation/11-templating/src/.gitkeep create mode 100644 implementation/11-templating/src/Action/Hello.php create mode 100644 implementation/11-templating/src/Action/Other.php create mode 100644 implementation/11-templating/src/Bootstrap.php create mode 100644 implementation/11-templating/src/Exception/InternalServerError.php create mode 100644 implementation/11-templating/src/Exception/MethodNotAllowed.php create mode 100644 implementation/11-templating/src/Exception/NotFound.php create mode 100644 implementation/11-templating/src/Service/Time/Now.php create mode 100644 implementation/11-templating/src/Service/Time/SystemClockNow.php create mode 100644 implementation/11-templating/src/Settings.php create mode 100644 implementation/11-templating/src/Template/MustacheRenderer.php create mode 100644 implementation/11-templating/src/Template/Renderer.php create mode 100644 implementation/11-templating/templates/hello.html create mode 100644 implementation/12-configuration/.php-cs-fixer.php create mode 100644 implementation/12-configuration/.phpcs.xml.dist create mode 100644 implementation/12-configuration/composer.json create mode 100644 implementation/12-configuration/composer.lock create mode 100644 implementation/12-configuration/config/dependencies.php create mode 100644 implementation/12-configuration/config/routes.php create mode 100644 implementation/12-configuration/config/settings.php create mode 100644 implementation/12-configuration/phpstan-baseline.neon create mode 100644 implementation/12-configuration/phpstan.neon create mode 100644 implementation/12-configuration/public/favicon.ico create mode 100644 implementation/12-configuration/public/index.php create mode 100644 implementation/12-configuration/src/.gitkeep create mode 100644 implementation/12-configuration/src/Action/Hello.php create mode 100644 implementation/12-configuration/src/Action/Other.php create mode 100644 implementation/12-configuration/src/Bootstrap.php create mode 100644 implementation/12-configuration/src/Exception/InternalServerError.php create mode 100644 implementation/12-configuration/src/Exception/MethodNotAllowed.php create mode 100644 implementation/12-configuration/src/Exception/NotFound.php create mode 100644 implementation/12-configuration/src/Factory/ContainerProvider.php create mode 100644 implementation/12-configuration/src/Factory/FileSystemSettingsProvider.php create mode 100644 implementation/12-configuration/src/Factory/SettingsContainerProvider.php create mode 100644 implementation/12-configuration/src/Factory/SettingsProvider.php create mode 100644 implementation/12-configuration/src/Service/Time/Now.php create mode 100644 implementation/12-configuration/src/Service/Time/SystemClockNow.php create mode 100644 implementation/12-configuration/src/Settings.php create mode 100644 implementation/12-configuration/src/Template/MustacheRenderer.php create mode 100644 implementation/12-configuration/src/Template/Renderer.php create mode 100644 implementation/12-configuration/templates/hello.html create mode 100644 implementation/13-refactoring/.php-cs-fixer.php create mode 100644 implementation/13-refactoring/.phpcs.xml.dist create mode 100644 implementation/13-refactoring/composer.json create mode 100644 implementation/13-refactoring/composer.lock create mode 100644 implementation/13-refactoring/config/dependencies.php create mode 100644 implementation/13-refactoring/config/routes.php create mode 100644 implementation/13-refactoring/config/settings.php create mode 100644 implementation/13-refactoring/phpstan-baseline.neon create mode 100644 implementation/13-refactoring/phpstan.neon create mode 100644 implementation/13-refactoring/public/favicon.ico create mode 100644 implementation/13-refactoring/public/index.php create mode 100644 implementation/13-refactoring/src/.gitkeep create mode 100644 implementation/13-refactoring/src/Action/Hello.php create mode 100644 implementation/13-refactoring/src/Action/Other.php create mode 100644 implementation/13-refactoring/src/Bootstrap.php create mode 100644 implementation/13-refactoring/src/Exception/InternalServerError.php create mode 100644 implementation/13-refactoring/src/Exception/MethodNotAllowed.php create mode 100644 implementation/13-refactoring/src/Exception/NotFound.php create mode 100644 implementation/13-refactoring/src/Factory/ContainerProvider.php create mode 100644 implementation/13-refactoring/src/Factory/DiactorosRequestFactory.php create mode 100644 implementation/13-refactoring/src/Factory/FileSystemSettingsProvider.php create mode 100644 implementation/13-refactoring/src/Factory/RequestFactory.php create mode 100644 implementation/13-refactoring/src/Factory/SettingsContainerProvider.php create mode 100644 implementation/13-refactoring/src/Factory/SettingsProvider.php create mode 100644 implementation/13-refactoring/src/Http/BasicEmitter.php create mode 100644 implementation/13-refactoring/src/Http/Emitter.php create mode 100644 implementation/13-refactoring/src/Http/InvokerRoutedHandler.php create mode 100644 implementation/13-refactoring/src/Http/RouteMiddleware.php create mode 100644 implementation/13-refactoring/src/Http/RoutedRequestHandler.php create mode 100644 implementation/13-refactoring/src/Kernel.php create mode 100644 implementation/13-refactoring/src/Service/Time/Now.php create mode 100644 implementation/13-refactoring/src/Service/Time/SystemClockNow.php create mode 100644 implementation/13-refactoring/src/Settings.php create mode 100644 implementation/13-refactoring/src/Template/MustacheRenderer.php create mode 100644 implementation/13-refactoring/src/Template/Renderer.php create mode 100644 implementation/13-refactoring/templates/hello.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cc205bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +**/vendor/ +**/.php-cs-fixer.cache +.idea/ +.vagrant/ \ No newline at end of file diff --git a/01-front-controller.md b/01-front-controller.md index 0006f1e..87a12ad 100644 --- a/01-front-controller.md +++ b/01-front-controller.md @@ -17,7 +17,7 @@ So instead of doing that, create a folder in your project folder called `public` Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: ```php -=7.0.0" - }, - "autoload": { - "psr-4": { - "Example\\": "src/" - } + "name": "lubian/no-framework", + "require": { + "php": "^8.1" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" } + }, + "authors": [ + { + "name": "lubiana", + "email": "lubiana@hannover.ccc.de" + } + ] } ``` -In the autoload part you can see that I am using the `Example` namespace for the project. You can use whatever fits your project there, but from now on I will always use the `Example` namespace in my examples. Just replace it with your namespace in your own code. +In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use +whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. +Just replace it with your namespace in your own code. + +I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. + +As the Bootstrap.php file is placed in that directory we should +add the namespace to the File as well. Here is my current Bootstrap.php +as a reference: + +```php +>](04-http.md) +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) ### Error Handler An error handler allows you to customize what happens if your code results in an error. -A nice error page with a lot of information for debugging goes a long way during development. So the first package for your application will take care of that. +A nice error page with a lot of information for debugging goes a long way during development. So the first package +for your application will take care of that. -I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, you have total control over your project. +I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. +If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, +you have total control over your project. An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) -To install a new package, open up your `composer.json` and add the package to the require part. It should now look like this: +To install a new package, open up your `composer.json` and add the package to the require part. It should now look +like this: ```php "require": { - "php": ">=7.0.0", - "filp/whoops": "~2.1" + "php": ">=8.1.0", + "filp/whoops": "^2.14" }, ``` Now run `composer update` in your console and it will be installed. -But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. +Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, +i that case composer automatically installs the package and updates your composer.json-file. -**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message can help someone to gain access to your system. Always show a user friendly error page instead and send an email to yourself, write to a log or something similar. So only you can see the errors in the production environment. +But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, +ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you +only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. -For development that does not make sense though -- you want a nice error page. The solution is to have an environment switch in your code. For now you can just set it to `development`. +**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message +can help someone to gain access to your system. Always show a user friendly error page instead and send an email to +yourself, write to a log or something similar. So only you can see the errors in the production environment. -Then after the error handler registration, throw an `Exception` to test if everything is working correctly. Your `Bootstrap.php` should now look similar to this: +For development that does not make sense though -- you want a nice error page. The solution is to have an environment +switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in +case no environment has been set. + +Then after the error handler registration, throw an `Exception` to test if everything is working correctly. +Your `Bootstrap.php` should now look similar to this: ```php -pushHandler(new \Whoops\Handler\PrettyPageHandler); +$whoops = new Run; +if ($environment == 'dev') { + $whoops->pushHandler(new PrettyPageHandler); } else { - $whoops->pushHandler(function($e){ - echo 'Todo: Friendly error page and send an email to the developer'; + $whoops->pushHandler(function (\Throwable $e) { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; }); } $whoops->register(); -throw new \Exception; +throw new \Exception("Ooooopsie"); ``` -You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until you get it working. Now would also be a good time for another commit. +You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until +you get it working. Now would also be a good time for another commit. -[<< previous](02-composer.md) | [next >>](04-http.md) + +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/04-development-helpers.md b/04-development-helpers.md new file mode 100644 index 0000000..74f913c --- /dev/null +++ b/04-development-helpers.md @@ -0,0 +1,260 @@ +[<< previous](03-error-handler.md) | [next >>](05-http.md) + +### Development Helpers + +I have added some more helpers to my composer.json that help me with development. As these are scripts and programms +used only for development they should not be used in a production environment. Composer has a specific sections in its +file called "dev-dependencies", everything that is required in this section does not get installen in production. + +Let's install our dev-helpers and i will explain them one by one: +`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` + +#### Static Code Analysis with phpstan + +Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or +create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements +that are not (or not always) available, if-statements that always are true and a lot of other stuff. + +A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. + +```php +/** + * @param \DateTime $date + * @return void + */ +function printDate($date) { + $date->format('Y-m-d H:i:s'); +} + +printDate('now'); +``` +if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` + +It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has +no use, and secondly, that we are calling the function with a string instead of a datetime object. + +```shell +1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + ------ --------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ --------------------------------------------------------------------------------------------- +30 Call to method DateTime::format() on a separate line has no effect. +33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. + ------ --------------------------------------------------------------------------------------------- +``` + +The second error is something that "declare strict-types" already catches for us, but the first error is something that +we usually would not discover easily without speccially looking for this errortype. + +We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and +path everytime we want to check our code for errors: + +```yaml +parameters: + level: max + paths: + - src +``` +now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project + +With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us +some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. + +```shell +composer require --dev phpstan/extension-installer +composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules +``` + +During the first install you need to allow the extension installer to actually install the extension. The second command +installs some more strict rulesets and activates them in phpstan. + +If we now rerun phpstan it already tells us about some errors we have made: + +``` + ------ ----------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ ----------------------------------------------------------------------------------------------- +10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider + using long ternary. +25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: + http://bit.ly/subtypeexception +26 Unreachable statement - code above always terminates. + ------ ----------------------------------------------------------------------------------------------- +``` + +The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove +that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific +problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working +on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages +everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we +are adding to our code. + +In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the +phpstan.neon file and run phpstan with the +'--generate-baseline' option: + +```yaml +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` +```shell +[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline +Note: Using configuration file /home/vagrant/app/phpstan.neon. + 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + + + [OK] Baseline generated with 1 error. + + +``` + +you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) + +#### PHP-CS-Fixer + +Another great tool is the php-cs-fixer, which just applies a specific style to your code. + +when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current +directory. + +You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) + +personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup +a configuration file that i use in all my projects .php-cs-fixer.php: + +```php +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + ]) + ); +``` + +#### PHP Codesniffer + +The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra +rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. + +it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. + +Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have +guessed we are adding some extra rules. + +Lets install the ruleset with composer +```shell +composer require --dev mnapoli/hard-mode +``` + +and add a configuration file to actually use it '.phpcs.xml.dist' +```xml + + + + + src + + + +``` + +running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. + +``` +[vagrant@archlinux app]$ ./vendor/bin/phpcs + +FILE: src/Bootstrap.php +---------------------------------------------------------------------------------------------------- +FOUND 4 ERRORS AFFECTING 4 LINES +---------------------------------------------------------------------------------------------------- + 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. + 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead +---------------------------------------------------------------------------------------------------- +PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY +---------------------------------------------------------------------------------------------------- + +Time: 639ms; Memory: 10MB +``` + +You can then use `./vendor/bin/phpcbf` to try to fix them + + +#### Symfony Var-Dumper + +another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small +functions. + +dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects +or arrays. + +dd() on the other hand is a function that dumps its parameters and then exits the php-script. + +you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. + +#### Composer scripts + +now we have a few commands that are available on the command line. i personally do not like to type complex commands +with lots of parameters by hand all the time, so i added a few lines to my composer.json: + +```json +"scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" +}, +``` + +that way i can just type "composer" followed by the command name in the root of my project. if i want to start the +php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the +time. + +You could also configure PhpStorm to automatically run these commands in the background and highlight the violations +directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my +flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. + +My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before +commiting and pushing the code. + +[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/04-http.md b/04-http.md deleted file mode 100644 index cf9226d..0000000 --- a/04-http.md +++ /dev/null @@ -1,66 +0,0 @@ -[<< previous](03-error-handler.md) | [next >>](05-router.md) - -### HTTP - -PHP already has a few things built in to make working with HTTP easier. For example there are the [superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. - -These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, then you will want a class with a nice object-oriented interface that you can use in your application instead. - -Once again, you don't have to reinvent the wheel and just install a package. I decided to write my own [HTTP component](https://github.com/PatrickLouys/http) because I did not like the existing components, but you don't have to do the same. - -Some alternatives: [Symfony HttpFoundation](https://github.com/symfony/HttpFoundation), [Nette HTTP Component](https://github.com/nette/http), [Aura Web](https://github.com/auraphp/Aura.Web), [sabre/http](https://github.com/fruux/sabre-http) - -In this tutorial I will use my own HTTP component, but of course you can use any package that you like. You just have to adapt the code from the tutorial yourself. - -Again, edit the `composer.json` to add the new component and then run `composer update`: - -```json - "require": { - "php": ">=7.0.0", - "filp/whoops": "~2.1", - "patricklouys/http": "~1.4" - }, -``` - -Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): - -```php -$request = new \Http\HttpRequest($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER); -$response = new \Http\HttpResponse; -``` - -This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. - -To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: - -```php -foreach ($response->getHeaders() as $header) { - header($header, false); -} - -echo $response->getContent(); -``` - -This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only stores data. This is handled differently by most other HTTP components where the classes send data back to the browser as a side-effect, so keep that in mind if you use another component. - -The second parameter of `header()` is false because otherwise existing headers will be overwritten. - -Right now it is just sending an empty response back to the browser with the status code `200`; to change that, add the following code between the code snippets from above (just on top of the `foreach` statement): - -```php -$content = '

Hello World

'; -$response->setContent($content); -``` - -If you want to try a 404 error, use the following code: - -```php -$response->setContent('404 - Page not found'); -$response->setStatusCode(404); -``` - -Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. - -I will show you in later parts how to use the different features of the components. In the meantime, feel free to read the [documentation](https://github.com/PatrickLouys/http) or the source code if you want to find out how something works. - -[<< previous](03-error-handler.md) | [next >>](05-router.md) diff --git a/05-http.md b/05-http.md new file mode 100644 index 0000000..6166214 --- /dev/null +++ b/05-http.md @@ -0,0 +1,124 @@ +[<< previous](04-development-helpers.md) | [next >>](06-router.md) + +### HTTP + +PHP already has a few things built in to make working with HTTP easier. For example there are the +[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. + +These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, +if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, +then you will want a class with a nice object-oriented interface that you can use in your application instead. + +Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The +standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php +projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. + +As this is a widely adopted standard there are already several implementations available for us to use. I will choose +the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. + +Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a +[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. + +Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use +that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding +the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). + + +to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit +enter + +Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): + +```php +$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); +$response = new \Laminas\Diactoros\Response; +$response->getBody()->write('Hello World! '); +$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); +``` + +This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. + +In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the +write()-Method on that object. + + +To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: + +```php +echo $response->getBody(); +``` + +This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only +stores data. + +You can play around with the other methods of the Request object and take a look at its content with the dd() function. + +```php +dd($response) +``` + +Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot +be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which +creates a copy of the request object with the changed property and returns that clone: + +```php +$response = $response->withStatus(200); +$response = $response->withAddedHeader('Content-type', 'application/json'); +``` + +If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been +defined this way. + +But if you have been keeping attention you might argue that the following line should not work if the request object is +immutable. + +```php +$response->getBody()->write('Hello World!'); +``` + +The response-body implements a stream interface which is immutable for some reasons that are described in the +[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be +aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in +the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. +I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content +to a response object. + +```php +$body = $response->getBody(); +$body->write('Hello World!'); +$response = $response->withBody($body); +``` + +Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our +output-logic a little bit more. Replace the line that echos the response-body with the following: + +```php +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); +``` + +This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a +webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. + +Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. + +Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter + + +[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/05-router.md b/05-router.md deleted file mode 100644 index 44c4ddb..0000000 --- a/05-router.md +++ /dev/null @@ -1,81 +0,0 @@ -[<< previous](04-http.md) | [next >>](06-dispatching-to-a-class.md) - -### Router - -A router dispatches to different handlers depending on rules that you have set up. - -With your current setup it does not matter what URL is used to access the application, it will always result in the same response. So let's fix that now. - -I will use [FastRoute](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own favorite package. - -Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) - -By now you know how to install Composer packages, so I will leave that to you. - -Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. - -```php -$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello-world', function () { - echo 'Hello World'; - }); - $r->addRoute('GET', '/another-route', function () { - echo 'This works too'; - }); -}); - -$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getPath()); -switch ($routeInfo[0]) { - case \FastRoute\Dispatcher::NOT_FOUND: - $response->setContent('404 - Page not found'); - $response->setStatusCode(404); - break; - case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: - $response->setContent('405 - Method not allowed'); - $response->setStatusCode(405); - break; - case \FastRoute\Dispatcher::FOUND: - $handler = $routeInfo[1]; - $vars = $routeInfo[2]; - call_user_func($handler, $vars); - break; -} -``` - -In the first part of the code, you are registering the available routes for your application. In the second part, the dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, the handler callable will be executed. - -This setup might work for really small applications, but once you start adding a few routes your bootstrap file will quickly get cluttered. So let's move them out into a separate file. - -Create a `Routes.php` file in the `src/` folder. It should look like this: - -```php -addRoute($route[0], $route[1], $route[2]); - } -}; - -$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); -``` - -This is already an improvement, but now all the handler code is in the `Routes.php` file. This is not optimal, so let's fix that in the next part. - -Don't forget to commit your changes at the end of each chapter. - -[<< previous](04-http.md) | [next >>](06-dispatching-to-a-class.md) diff --git a/06-dispatching-to-a-class.md b/06-dispatching-to-a-class.md deleted file mode 100644 index 7df552a..0000000 --- a/06-dispatching-to-a-class.md +++ /dev/null @@ -1,56 +0,0 @@ -[<< previous](05-router.md) | [next >>](07-inversion-of-control.md) - -### Dispatching to a Class - -In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) and the followup posts. - -So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). - -We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Controllers` because that will be familiar for the people coming from a framework background. You could also name them `Handlers`. - -Create a new folder inside the `src/` folder with the name `Controllers`.In this folder we will place all our controller classes. In there, create a `Homepage.php` file. - -```php -$method($vars); - break; -``` - -So instead of just calling a method you are now instantiating an object and then calling the method on it. - -Now if you visit `http://localhost:8000/` everything should work. If not, go back and debug. And of course don't forget to commit your changes. - -[<< previous](05-router.md) | [next >>](07-inversion-of-control.md) diff --git a/06-router.md b/06-router.md new file mode 100644 index 0000000..6c39ae5 --- /dev/null +++ b/06-router.md @@ -0,0 +1,101 @@ +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) + +### Router + +A router dispatches to different handlers depending on rules that you have set up. + +With your current setup it does not matter what URL is used to access the application, it will always result in the same +response. So let's fix that now. + +I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own +favorite package. + +Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) + +By now you know how to install Composer packages, so I will leave that to you. + +Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. + +```php +$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +switch ($routeInfo[0]) { + case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: + $response = (new \Laminas\Diactoros\Response)->withStatus(405); + $response->getBody()->write('Method not allowed'); + $response = $response->withStatus(405); + break; + case \FastRoute\Dispatcher::FOUND: + $handler = $routeInfo[1]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + /** @var \Psr\Http\Message\ResponseInterface $response */ + $response = call_user_func($handler, $request); + break; + case \FastRoute\Dispatcher::NOT_FOUND: + default: + $response = (new \Laminas\Diactoros\Response)->withStatus(404); + $response->getBody()->write('Not Found!'); + break; +} +``` + +In the first part of the code, you are registering the available routes for your application. In the second part, the +dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, +we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. +If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. + +This setup might work for really small applications, but once you start adding a few routes your bootstrap file will +quickly get cluttered. So let's move them out into a separate file. + +Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; + +```php +addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}; +``` + +Now let's rewrite the route dispatcher part to use the `Routes.php` file. + +```php +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); +``` + +This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. + +Of course we now need to add the 'config' folder to the configuration files of our +devhelpers so that they can scan that directory as well. + +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/07-dispatching-to-a-class.md b/07-dispatching-to-a-class.md new file mode 100644 index 0000000..0c961a4 --- /dev/null +++ b/07-dispatching-to-a-class.md @@ -0,0 +1,137 @@ +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) + +### Dispatching to a Class + +In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). +MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to +learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) +and the followup posts. + +So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). + +We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other +common names are 'Controllers' or 'Actions'. + +Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. +In there, create a `Hello.php` file. + +```php +getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + } +} +``` + +You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) +that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is +fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement +it in some other parts of our application as well. In order to use that Interface we have to require it with composer: +'composer require psr/http-server-handler'. + +The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. +At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. + +Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: + +```php +return function(\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); + $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); +}; +``` + +Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared +the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing +the response to the one we defined for the second route. + +To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: + +```php +case \FastRoute\Dispatcher::FOUND: + $handler = new $routeInfo[1]; + if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { + throw new \Exception('Invalid Requesthandler'); + } + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + assert($response instanceof \Psr\Http\Message\ResponseInterface) + break; +``` + +So instead of just calling a method you are now instantiating an object and then calling the method on it. + +Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. + +And of course don't forget to commit your changes. + +Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still +generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for +example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still +have our whoopsie error-handler but i like to be more explicit in my control flow. + +In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new +Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and +InternalServerError. All three should extend phps Base Exception class. + +Here is my NotFound.php for example. + +```php + $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody()->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} +``` + +Check if our code still works, try to trigger some errors, run phpstan and the fix command +and don't forget to commit your changes. + +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/07-inversion-of-control.md b/07-inversion-of-control.md deleted file mode 100644 index 6149cc4..0000000 --- a/07-inversion-of-control.md +++ /dev/null @@ -1,51 +0,0 @@ -[<< previous](06-dispatching-to-a-class.md) | [next >>](08-dependency-injector.md) - -### Inversion of Control - -In the last part you have set up a controller class and generated output with `echo`. But let's not forget that we have a nice object oriented HTTP abstraction available. But right now it's not accessible inside your class. - -The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). - -If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is implemented, it will make sense. - -Change your `Homepage` controller to the following: - -```php -response = $response; - } - - public function show() - { - $this->response->setContent('Hello World'); - } -} -``` - -Note that we are [importing](http://php.net/manual/en/language.namespaces.importing.php) `Http\Response` at the top of the file. This means that whenever you use `Response` inside this file, it will resolve to the fully qualified name. - -In the constructor we are now explicitly asking for a `Http\Response`. In this case, `Http\Response` is an interface. So any class that implements the interface can be injected. See [type hinting](http://php.net/manual/en/language.oop5.typehinting.php) and [interfaces](http://php.net/manual/en/language.oop5.interfaces.php) for reference. - -Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: - -```php -$class = new $className($response); -$class->$method($vars); -``` - -The `Http\HttpResponse` object implements the `Http\Response` interface, so it fulfills the contract and can be used. - -Now everything should work again. But if you follow this example, all your objects that are instantiated this way will have the same objects injected. This is of course not good, so let's fix that in the next part. - -[<< previous](06-dispatching-to-a-class.md) | [next >>](08-dependency-injector.md) diff --git a/08-dependency-injector.md b/08-dependency-injector.md deleted file mode 100644 index 4469392..0000000 --- a/08-dependency-injector.md +++ /dev/null @@ -1,97 +0,0 @@ -[<< previous](07-inversion-of-control.md) | [next >>](09-templating.md) - -### Dependency Injector - -A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when the class is instantiated. - -There is only one injector that I can recommend: [Auryn](https://github.com/rdlowrey/Auryn). Sadly all the alternatives that I am aware of are using the [service locator antipattern](http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/) in their documentation and examples. - -Install the Auryn package and then create a new file called `Dependencies.php` in your `src/` folder. In there add the following content: - -```php -alias('Http\Request', 'Http\HttpRequest'); -$injector->share('Http\HttpRequest'); -$injector->define('Http\HttpRequest', [ - ':get' => $_GET, - ':post' => $_POST, - ':cookies' => $_COOKIE, - ':files' => $_FILES, - ':server' => $_SERVER, -]); - -$injector->alias('Http\Response', 'Http\HttpResponse'); -$injector->share('Http\HttpResponse'); - -return $injector; -``` - -Make sure you understand what `alias`, `share` and `define` are doing before you continue. You can read about them in the [Auryn documentation](https://github.com/rdlowrey/Auryn). - -You are sharing the HTTP objects because there would not be much point in adding content to one object and then returning another one. So by sharing it you always get the same instance. - -The alias allows you to type hint the interface instead of the class name. This makes it easy to switch the implementation without having to go back and edit all your classes that use the old implementation. - -Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the injector now so that we are using the same instance of those objects everywhere. - -```php -$injector = include('Dependencies.php'); - -$request = $injector->make('Http\HttpRequest'); -$response = $injector->make('Http\HttpResponse'); -``` - -The other part that has to be changed is the dispatching of the route. Before you had the following code: - -```php -$class = new $className($response); -$class->$method($vars); -``` - -Change that to the following: - -```php -$class = $injector->make($className); -$class->$method($vars); -``` - -Now all your controller constructor dependencies will be automatically resolved with Auryn. - -Go back to your `Homepage` controller and change it to the following: - -```php -request = $request; - $this->response = $response; - } - - public function show() - { - $content = '

Hello World

'; - $content .= 'Hello ' . $this->request->getParameter('name', 'stranger'); - $this->response->setContent($content); - } -} -``` - -As you can see now the class has two dependencies. Try to access the page with a GET parameter like this `http://localhost:8000/?name=Arthur%20Dent`. - -Congratulations, you have now successfully laid the groundwork for your application. - -[<< previous](07-inversion-of-control.md) | [next >>](09-templating.md) diff --git a/08-inversion-of-control.md b/08-inversion-of-control.md new file mode 100644 index 0000000..21f4f23 --- /dev/null +++ b/08-inversion-of-control.md @@ -0,0 +1,54 @@ +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) + +### Inversion of Control + +In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we +want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then +we would need to edit every one of our request handlers to call a different constructor of the class. + +The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that +instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done +with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). + +If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is +implemented, it will make sense. + +Change your `Hello` action to the following: + +```php +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: + +```php +$handler = new $className($response); +``` + +Of course we need to also update all the other handlers. + +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/09-dependency-injector.md b/09-dependency-injector.md new file mode 100644 index 0000000..7f7c6a2 --- /dev/null +++ b/09-dependency-injector.md @@ -0,0 +1,213 @@ +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) + +### Dependency Injector + +A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when +the class is instantiated. + +Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work +with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look +for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). + +I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) +out of the box. + +After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: + +```php +addDefinitions([ + \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), +]); + +return $builder->build(); +``` + +In this file we create a containerbuilder, add some definitions to it and return the container. +As the container supports autowiring we only need to define services where we want to use a specific implementation of +an interface. + +In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to +tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second +exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. + +Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), +as we will use that extensively. + +Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally +to create our Handler-Object + +```php +$container = require __DIR__ . '/../config/dependencies.php'; +assert($container instanceof \Psr\Container\ContainerInterface); + +$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); +assert($request instanceof \Psr\Http\Message\ServerRequestInterface); +``` + +The other part that has to be changed is the dispatching of the route. Before you had the following code: + +```php +$className = $routeInfo[1]; +$handler = new $className($response); +assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Change that to the following: + +```php +/** @var RequestHandlerInterface $handler */ +$className = $routeInfo[1]; +$handler = $container->get($className); +assert($handler instanceof RequestHandlerInterface); +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Make sure to use the container fetch the response object in the catch blocks as well: + +```php +} catch (MethodNotAllowed) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(404); + $response->getBody()->write('Not Found'); +} +``` + +Now all your controller constructor dependencies will be automatically resolved with PHP-DI. + +We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons +in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call +`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we +want to work with a different date in a test. + +Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation +that creates us a DateTimeImmutable object with the current date and time. + +src/Service/Time/Now.php: +```php +namespace Lubian\NoFramework\Service\Time; + +interface Now +{ + public function __invoke(): \DateTimeImmutable; +} +``` +src/Service/Time/SystemClockNow.php: +```php +namespace Lubian\NoFramework\Service\Time; + +final class SystemClockNow implements Now +{ + + public function __invoke(): \DateTimeImmutable + { + return new \DateTimeImmutable; + } +} +``` +If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and +update the handle-method to use the new class property: + +```php +getAttribute('name', 'Stranger'); + $nowAsString = ($this->now)()->format('H:i:s'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowAsString); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI +automatically figures out what classes are requested in the constructor and tries to create the objects needed. + +But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred +S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: + +```php + public function __construct( + private ResponseInterface $response, + private Now $now, + ) +``` + +When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation +should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: + +```php +\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), +``` + +we could also use the PHP-DI create method to delegate the object creation to the container implementation: +```php +\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), +``` + +this way the container can try to resolve any dependencies that the class might have internally, but prefer the other +method because we are not depending on this specific dependency injection implementation. + +Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are +requesting the Hello action. + +If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As +we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way +we wont get any warnings for this particular issue, but for any other issues we add to our code. + +Update the phpstan.neon file to include a "baseline" file: + +``` +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` + +if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and +ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just +'baseline' + +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/09-templating.md b/09-templating.md deleted file mode 100644 index 08720de..0000000 --- a/09-templating.md +++ /dev/null @@ -1,141 +0,0 @@ -[<< previous](08-dependency-injector.md) | [next >>](10-dynamic-pages.md) - -### Templating - -A template engine is not necessary with PHP because the language itself can take care of that. But it can make things like escaping values easier. They also make it easier to draw a clear line between your application logic and the template files which should only put your variables into the HTML code. - -A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. - -For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install that package before you continue (`composer require mustache/mustache`). - -Another well known alternative would be [Twig](http://twig.sensiolabs.org/). - -Now please go and have a look at the source code of the [engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class does not implement an interface. - -You could just type hint against the concrete class. But the problem with this approach is that you create tight coupling. - -In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want to add functionality to the engine. You can't do that without going back and changing all your code that is tightly coupled. - -What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need another implementation, you just implement that interface in your new class and inject the new class instead. - -Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). This sounds a lot more complicated than it is, so just follow along. - -First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. A class can extend multiple interfaces if necessary. - -So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a new folder in your `src/` folder with the name `Template` where you can put all the template related things. - -In there create a new interface `Renderer.php` that looks like this: - -```php -engine = $engine; - } - - public function render($template, $data = []) : string - { - return $this->engine->render($template, $data); - } -} -``` - -As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple and only fulfills the interface. - -Of course we also have to add a definition in our `Dependencies.php` file because otherwise the injector won't know which implementation he has to inject when you hint for the interface. Add this line: - -`$injector->alias('Example\Template\Renderer', 'Example\Template\MustacheRenderer');` - -Now in your `Homepage` controller, add the new dependency like this: - -```php -request = $request; - $this->response = $response; - $this->renderer = $renderer; - } - -... -``` - -We also have to rewrite the `show` method. Please note that while we are just passing in a simple array, Mustache also gives you the option to pass in a view context object. We will go over this later, for now let's keep it as simple as possible. - -```php - public function show() - { - $data = [ - 'name' => $this->request->getParameter('name', 'stranger'), - ]; - $html = $this->renderer->render('Hello {{name}}', $data); - $this->response->setContent($html); - } -``` - -Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. But what we want is template files, so let's go back and change that. - -To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the `Dependencies.php` file and add the following code: - -```php -$injector->define('Mustache_Engine', [ - ':options' => [ - 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__DIR__) . '/templates', [ - 'extension' => '.html', - ]), - ], -]); -``` - -We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have to rename all the template files. - -In your project root folder, create a `templates` folder. In there, create a file `Homepage.html`. The content of the file should look like this: - -``` -

Hello World

-Hello {{ name }} -``` - -Now you can go back to your `Homepage` controller and change the render line to `$html = $this->renderer->render('Homepage', $data);` - -Navigate to the hello page in your browser to make sure everything works. And as always, don't forget to commit your changes. - -[<< previous](08-dependency-injector.md) | [next >>](10-dynamic-pages.md) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md deleted file mode 100644 index 398ba81..0000000 --- a/10-dynamic-pages.md +++ /dev/null @@ -1,205 +0,0 @@ -[<< previous](09-templating.md) | [next >>](11-page-menu.md) - -### Dynamic Pages - -So far we only have a static page with not much functionality. Just having a hello world example is not very useful, so let's go beyond that and add some real functionality to our application. - -Our first feature will be dynamic pages generated from [markdown](http://en.wikipedia.org/wiki/Markdown) files. - -Create a `Page` controller with the following content: - -```php -pageFolder = $pageFolder; - } - - public function readBySlug(string $slug) : string - { - return 'I am a placeholder'; - } -} -``` - -As you can see we are requiring the page folder path as a constructor argument. This makes the class flexible and if we decide to move files or write unit tests for the class, we can easily change the location with the constructor argument. - -You could also put the page related things into it's own package and reuse it in different applications. Because we are not tightly coupling things, things are very flexible. - -This will do for now. Let's create a template file for our pages with the name `Page.html` in the `templates` folder. For now just add `{{ content }}` in there. - -Add the following to your `Dependencies.php` file so that the application know which implementation to inject for our new interface. We also define the the `pageFolder` there. - -```php -$injector->define('Example\Page\FilePageReader', [ - ':pageFolder' => __DIR__ . '/../pages', -]); - -$injector->alias('Example\Page\PageReader', 'Example\Page\FilePageReader'); -$injector->share('Example\Page\FilePageReader'); -``` - - -Now go back to the `Page` controller and change the `show` method to the following: - -```php -public function show($params) -{ - $slug = $params['slug']; - $data['content'] = $this->pageReader->readBySlug($slug); - $html = $this->renderer->render('Page', $data); - $this->response->setContent($html); -} -``` - -To make this work, we will need to inject a `Response`, `Renderer` and a `PageReader`. I will leave this to you as an exercise. Remember to `use` all the proper namespaces. Use the `Homepage` controller as a reference. - -Did you get everything to work? - -If not, this is how the beginning of your controller should look now: - -```php -response = $response; - $this->renderer = $renderer; - $this->pageReader = $pageReader; - } -... -``` - -So far so good, now let's make our `FilePageReader` actually do some work. - -We need to be able to communicate that a page was not found. For this we can create a custom exception that we can catch later. In your `src/Page` folder, create a `InvalidPageException.php` file with this content: - -```php -pageFolder/$slug.md"; - -if (!file_exists($path)) { - throw new InvalidPageException($slug); -} - -return file_get_contents($path); -``` - -Now if you navigate to a page that does not exist, you should see an `InvalidPageException`. If a file exists, you should see the content. - -Of course showing the user an exception for an invalid URL does not make sense. So let's catch the exception and show a 404 error instead. - -Go to your `Page` controller and refactor the `show` method so that it looks like this: - -```php -public function show($params) -{ - $slug = $params['slug']; - - try { - $data['content'] = $this->pageReader->readBySlug($slug); - } catch (InvalidPageException $e) { - $this->response->setStatusCode(404); - return $this->response->setContent('404 - Page not found'); - } - - $html = $this->renderer->render('Page', $data); - $this->response->setContent($html); -} -``` - -Make sure that you use an `use` statement for the `InvalidPageException` at the top of the file. - -Try a few different URLs to check that everything is working as it should. If something is wrong, go back and debug it until it works. - -And as always, don't forget to commit your changes. - -[<< previous](09-templating.md) | [next >>](11-page-menu.md) diff --git a/10-invoker.md b/10-invoker.md new file mode 100644 index 0000000..3033fae --- /dev/null +++ b/10-invoker.md @@ -0,0 +1,102 @@ +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) + +### Invoker + +Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the +one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long +with some services and not the whole Requestobject with all its various properties. + +If we take our Hello action for example we only need a response object, the time service and the 'name' information from +the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named +the class hello and it would be redundant to also call the the method hello. So an updated version of that class could +look like this: + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + string $name = 'Stranger', + ): ResponseInterface + { + $body = $this->response->getBody(); + $nowString = $now->get()->format('H:i:s'); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowString); + return $response + ->withBody($body) + ->withStatus(200); + } +} +``` + +It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short +closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the +rootpath of our application yet. + +```php +$r->addRoute('GET', '/hello[/{name}]', Hello::class); +$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); +$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +``` + +In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the +route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that +class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what +arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router +if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple +callable we would need to manually use reflection as well to resolve all the arguments. + +But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) +which handles all of that for us. + +After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo +switch section in the Bootstrap.php file to use the container->call() method: + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2]; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$response = $container->call($handler, $args); +``` + +Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. + +But by now you should know that I do not like to depend on specific implementations and the call method is not defined in +the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container +or any other implementation. + +Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific +container implementation so we could use it with any container that implements the ContainerInterface. And best of all +the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that +we could implement if we ever want to write our own implementation or we could write an adapter that uses a different +class that solves the same problem. + +But for now we are using the solution provided by PHP-DI. +So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2] ?? []; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$invoker = $container->get(InvokerInterface::class); +assert($invoker instanceof InvokerInterface); +$response = $invoker->call($handler, $args); +assert($response instanceof ResponseInterface); +``` + +Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) +by php, and even some more. + +But let us move on to something more fun and add some templating functionality to our application as we are trying to build +a website in the end. + +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/11-page-menu.md b/11-page-menu.md deleted file mode 100644 index d45849e..0000000 --- a/11-page-menu.md +++ /dev/null @@ -1,260 +0,0 @@ -[<< previous](10-dynamic-pages.md) | [next >>](12-frontend.md) - -### Page Menu - -Now we have made a few nice dynamic pages. But nobody can find them. - -Let's fix that now. In this chapter we will create a menu with links to all of our pages. - -When we have a menu, we will want to be able to reuse the same code on multiple pages. We could create a separate file and include it every time, but there is a better solution. - -It is more practical to have templates that are able to extend other templates, like a layout for example. Then we can have all the layout related code in a single file and we don't have to include header and footer files in every template. - -Our implementation of mustache does not support this. We could write code to work around this, which will take time and could introduce some bugs. Or we could switch to a library that already supports this and is well tested. [Twig](http://twig.sensiolabs.org/) for example. - -Now you might wonder why we didn't start with Twig right away. Because this is a good example to show why using interfaces and writing loosely-coupled code is a good idea. Like in the real world, the requirements suddenly changed and now our code needs to adapt. - -Remember how you created a `MustacheRenderer` in [chapter 9](09-templating.md)? This time, we create a `TwigRenderer` that implements the same interface. - -But before we start, install the latest version of Twig with composer (`composer require "twig/twig:~1.0"`). - -Then create the a `TwigRenderer.php` in your `src/Template` folder that looks like this: - -```php -renderer = $renderer; - } - - public function render($template, $data = []) : string - { - return $this->renderer->render("$template.html", $data); - } -} -``` - -As you can see, on the render function call a `.html` is added. This is because Twig does not add a file ending by default and you would have to specifiy it on every call otherwise. By doing it like this, you can use it in the same way as you used Mustache. - -Add the following code to your `Dependencies.php` file: - -```php -$injector->delegate('Twig_Environment', function () use ($injector) { - $loader = new Twig_Loader_Filesystem(dirname(__DIR__) . '/templates'); - $twig = new Twig_Environment($loader); - return $twig; -}); -``` - -Instead of just defining the dependencies, we are using a delegate to give the responsibility to create the class to a function. This will be useful in the future. - -Now you can switch the `Renderer` alias from `MustacheRenderer` to `TwigRenderer`. Now by default Twig will be used instead of Mustache. - -If you have a look at the site in your browser, everything should work now as before. Now let's get started with the actual menu. - -To start we will just send a hardcoded array to the template. Go to you `Homepage` controller and change your `$data` array to this: - -```php -$data = [ - 'name' => $this->request->getParameter('name', 'stranger'), - 'menuItems' => [['href' => '/', 'text' => 'Homepage']], -]; -``` - -At the top of your `Homepage.html` file add this code: - -```php -{% for item in menuItems %} - {{ item.text }}
-{% endfor %} -``` - -Now if you refresh the homepage in the browser, you should see a link. - -The menu works on the homepage, but we want it on all our pages. We could copy it over to all the template files, but that would be a bad idea. Then if something changes, you would have to go change all the files. - -So instead we are going to use a layout that can be used by all the templates. - -Create a `Layout.html` in your `templates` folder with the following content: - -```php -{% for item in menuItems %} - {{ item['text'] }}
-{% endfor %} -
-{% block content %} -{% endblock %} -``` - -Then change your `Homepage.html` to this: - -```php -{% extends "Layout.html" %} -{% block content %} -

Hello World

- Hello {{ name }} -{% endblock %} -``` - -And your `Page.html` to this: - -```php -{% extends "Layout.html" %} -{% block content %} - {{ content }} -{% endblock %} -``` - -If you refresh your homepage now, you should see the menu. But if you go to a subpage, the menu is not there but the `
` line is. - -The problem is that we are only passing the `menuItems` to the homepage. Doing that over and over again for all pages would be a bit tedious and a lot of work if something changes. So let's fix that in the next step. - -We could create a global variable that is usable by all templates, but that is not a good idea here. We will add different parts of the site in the future like an admin area and we will have a different menu there. - -So instead we will use a custom renderer for the frontend. First we create an empty interface that extends the existing `Renderer` interface. - -```php -renderer = $renderer; - } - - public function render($template, $data = []) : string - { - $data = array_merge($data, [ - 'menuItems' => [['href' => '/', 'text' => 'Homepage']], - ]); - return $this->renderer->render($template, $data); - } -} -``` - -As you can see we have a dependency on a `Renderer` in this class. This class is a wrapper for our `Renderer` and adds the `menuItems` to all `$data` arrays. - -Of course we also need to add another alias to the dependencies file. - -```php -$injector->alias('Example\Template\FrontendRenderer', 'Example\Template\FrontendTwigRenderer'); -``` - -Now go to your controllers and exchange all references of `Renderer` with `FrontendRenderer`. Make sure you change it in both the `use` statement at the top and in the constructor. - -Also delete the following line from the `Homepage` controller: - -```php -'menuItems' => [['href' => '/', 'text' => 'Homepage']], -``` - -Once that is done, you should see the menu on both the homepage and your subpages. - -Everything should work now, but it doesn't really make sense that the menu is defined in the `FrontendTwigRenderer`. So let's refactor that and move it into it's own class. - -Right now the menu is defined in the array, but it is very likely that this will change in the future. Maybe you want to define it in the database or maybe you even want to generate it dynamically based on the pages available. We don't have this information and things might change in the future. - -So let's do the right thing here and start with an interface again. But first, create a new folder in the `src` directory for the menu related things. `Menu` sounds like a reasonable name, doesn't it? - -```php - '/', 'text' => 'Homepage'], - ]; - } -} -``` - -This is only a temporary solution to keep things moving forward. We are going to revisit this later. - -Before we continue, let's edit the dependencies file to make sure that our application knows which implementation to use when the interface is requested. - -Add these lines above the `return` statement: - -```php -$injector->alias('Example\Menu\MenuReader', 'Example\Menu\ArrayMenuReader'); -$injector->share('Example\Menu\ArrayMenuReader'); -``` - -Now you need to change out the hardcoded array in the `FrontendTwigRenderer` class to make it use our new `MenuReader` instead. Give it a try without looking at the solution below. - -Did you finish it or did you get stuck? Or are you just lazy? Doesn't matter, here is a working solution: - -```php -renderer = $renderer; - $this->menuReader = $menuReader; - } - - public function render($template, $data = []) : string - { - $data = array_merge($data, [ - 'menuItems' => $this->menuReader->readMenu(), - ]); - return $this->renderer->render($template, $data); - } -} -``` - -Everything still working? Awesome. Commit everything and move on to the next chapter. - -[<< previous](10-dynamic-pages.md) | [next >>](12-frontend.md) diff --git a/11-templating.md b/11-templating.md new file mode 100644 index 0000000..3759664 --- /dev/null +++ b/11-templating.md @@ -0,0 +1,240 @@ +[<< previous](10-invoker.md) | [next >>](12-configuration.md) + +### Templating + +A template engine is not necessary with PHP because the language itself can take care of that. But it can make things +like escaping values easier. They also make it easier to draw a clear line between your application logic and the +template files which should only put your variables into the HTML code. + +A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please +also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. +Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. + +For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install +that package before you continue (`composer require mustache/mustache`). + +Another well known alternative would be [Twig](http://twig.sensiolabs.org/). + +Now please go and have a look at the source code of the +[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class +does not implement an interface. + +You could just type hint against the concrete class. But the problem with this approach is that you create tight +coupling. + +In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the +implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want +to add functionality to the engine. You can't do that without going back and changing all your code that is tightly +coupled. + +What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need +another implementation, you just implement that interface in your new class and inject the new class instead. + +Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). +This sounds a lot more complicated than it is, so just follow along. + +First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). +This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. +A class can implement multiple interfaces if necessary. + +So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a +new folder in your `src/` folder with the name `Template` where you can put all the template related things. + +In there create a new interface `Renderer.php` that looks like this: + +```php + $data + * @return string + */ + public function render(string $template, array $data = []) : string; +} +``` + +Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file +`MustacheRenderer.php` with the following content: + +```php +engine->render($template, $data); + } +} +``` + +As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple +and only fulfills the interface. + +Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know +which implementation he has to inject when you hint for the interface. Add this line: + +```php +[ + ... + \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) + ->constructor(new Mustache_Engine), +] +``` + +Now update the Hello.php class to require an implementation of our renderer interface +and use that to render a string using mustache syntax. + + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + Renderer $renderer, + string $name = 'Stranger', + ): ResponseInterface { + $body = $response->getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render( + 'Hello {{name}}, the time is {{now}}!', + $data, + ); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} +``` + +Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. +But what we want is template files, so let's go back and change that. + +To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the +`dependencies.php` file and add the following code: + +```php +[ + ... + Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), + Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), +] +``` + +We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. +Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have +to rename all the template files. + +To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the +dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader +in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object +we can then use it in the factory. + +In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: + +``` +

Hello World

+Hello {{ name }} +``` + +Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` + +Navigate to the hello page in your browser to make sure everything works. + +One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies +file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration +values. + +Lets create a 'Settings' class in our './src' Folder: + +```php +addDefinitions([ + Settings::class => fn () => require __DIR__ '/settings.php', + ResponseInterface::class => create(Response::class), + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + Renderer::class => fn (ME $me) => new Mustache($me), + MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), +]); + +return $builder->build(); +``` + + + +And as always, don't forget to commit your changes. + + +[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/12-configuration.md b/12-configuration.md new file mode 100644 index 0000000..a44dfd5 --- /dev/null +++ b/12-configuration.md @@ -0,0 +1,201 @@ +[<< previous](11-templating.md) | [next >>](13-refactoring.md) + +### Configuration + +In the last chapter we added some more definitions to our dependencies.php in that definitions +we needed to pass quite a few configuration settings and filesystem strings to the constructors +of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. + +As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. + +As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. + +Lets create a 'Settings' class in our './src' Folder: + +```php +filePath; + } +} +``` + +If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files +and craft a settings object from them. + +As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another +factory for our Container because everyone should have a Friend :) + +```php +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn () => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} +``` + +For this to work we need to change our dependencies.php file to just return the array of definitions: +And here we can instantly use the Settings object to create our template engine. + +```php + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), +]; +``` + +Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: +require __DIR__ . '/../vendor/autoload.php'; + +```php +... +error_reporting(E_ALL); + +$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); +$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); +... +``` + +Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. + +[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/12-frontend.md b/12-frontend.md deleted file mode 100644 index bd30564..0000000 --- a/12-frontend.md +++ /dev/null @@ -1,188 +0,0 @@ -[<< previous](11-page-menu.md) | [next >>](to-be-continued.md) - - -### Frontend - -I don't know about you, but I don't like to work on a site that looks two decades old. So let's improve the look of our little application. - -This is not a frontend tutorial, so we'll just use [pure](http://purecss.io/) and call it a day. - -First we need to change the `Layout.html` template. I don't want to waste your time with HTML and CSS, so I'll just provide the code for you to copy paste. - -```php - - - - - Example - - - - -
- -
-
- {% block content %} - {% endblock %} -
-
-
- - -``` - -You will also need some custom CSS. This is a file that we want publicly accessible. So where do we put it? Exactly, in the public folder. - -But to keep things a little organized, add a `css` folder in there first and then create a `style.css` with the following content: - -```css -body { - color: #777; -} - -#layout { - position: relative; - padding-left: 0; -} - -#layout.active #menu { - left: 150px; - width: 150px; -} - -#layout.active .menu-link { - left: 150px; -} - -.content { - margin: 0 auto; - padding: 0 2em; - max-width: 800px; - margin-bottom: 50px; - line-height: 1.6em; -} - -.header { - margin: 0; - color: #333; - text-align: center; - padding: 2.5em 2em 0; - border-bottom: 1px solid #eee; -} - -.header h1 { - margin: 0.2em 0; - font-size: 3em; - font-weight: 300; -} - -.header h2 { - font-weight: 300; - color: #ccc; - padding: 0; - margin-top: 0; -} - -#menu { - margin-left: -150px; - width: 150px; - position: fixed; - top: 0; - left: 0; - bottom: 0; - z-index: 1000; - background: #191818; - overflow-y: auto; - -webkit-overflow-scrolling: touch; -} - -#menu a { - color: #999; - border: none; - padding: 0.6em 0 0.6em 0.6em; -} - -#menu .pure-menu, -#menu .pure-menu ul { - border: none; - background: transparent; -} - -#menu .pure-menu ul, -#menu .pure-menu .menu-item-divided { - border-top: 1px solid #333; -} - -#menu .pure-menu li a:hover, -#menu .pure-menu li a:focus { - background: #333; -} - -#menu .pure-menu-selected, -#menu .pure-menu-heading { - background: #1f8dd6; -} - -#menu .pure-menu-selected a { - color: #fff; -} - -#menu .pure-menu-heading { - font-size: 110%; - color: #fff; - margin: 0; -} - -.header, -.content { - padding-left: 2em; - padding-right: 2em; -} - -#layout { - padding-left: 150px; /* left col width "#menu" */ - left: 0; -} -#menu { - left: 150px; -} - -.menu-link { - position: fixed; - left: 150px; - display: none; -} - -#layout.active .menu-link { - left: 150px; -} -``` - -Now if you have a look at your site again, things should look a little better. Feel free to further improve the look of it yourself later. But let's continue with the tutorial now. - -Every time that you need an asset or a file publicly available, then you can just put it in your `public` folder. You will need this for all kinds of assets like javascript files, css files, images and more. - -So far so good, but it would be nice if our visitors can see what page they are on. - -Of course we need more than one page in the menu for this. I will just use the `page-one.md` that we created earlier in the tutorial. But feel free to add a few more pages and add them as well. - -Go back to the `ArrayMenuReader` and add your new pages to the array. It should look something like this now: - -```php -return [ - ['href' => '/', 'text' => 'Homepage'], - ['href' => '/page-one', 'text' => 'Page One'], -]; -``` - -[<< previous](11-page-menu.md) | [next >>](to-be-continued.md) - diff --git a/13-refactoring.md b/13-refactoring.md new file mode 100644 index 0000000..067e168 --- /dev/null +++ b/13-refactoring.md @@ -0,0 +1,377 @@ +[<< previous](12-configuration.md) | [next >>](14-middleware.md) + +### Refactoring + +By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no +reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. +After all the bootstrap file should just set up the classes needed for the handling logic and execute them. + +At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. +As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the +method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non +static and extracted an interface. + +'./src/Http/Emitter.php' +```php +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + echo $response->getBody(); + } +} +``` +After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following +code in the Bootstrap.php to emit the response: + +```php +/** @var Emitter $emitter */ +$emitter = $container->get(Emitter::class); +$emitter->emit($response); +``` + +If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily +write an adapter that implements your emitter interface and wraps that more advanced emitter + +Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and +calling the routerhandler that in the passes the request to a function and gets the response. + +For this to steps to be seperated we are going to create two more classes: +1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object +2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the + requestobject, fetches the correct object from the container and calls it to create a response. + +Lets create the HandlerInterface first: + +```php +getAttribute($this->routeAttributeName, false); + assert($handler !== false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} + +``` + +We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. +The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler +as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in +the next chapter. + +```php +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} +``` + +Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. +```php +[ + '...', + Emitter::class => fn (BasicEmitter $e) => $e, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, +], +``` + +And then we can update our Bootstrap.php to fetch all the services and let them handle the request. + +```php +... +$routeMiddleWare = $container->get(MiddlewareInterface::class); +assert($routeMiddleWare instanceof MiddlewareInterface); +$handler = $container->get(RoutedRequestHandler::class); +assert($handler instanceof RequestHandlerInterface); +$emitter = $container->get(Emitter::class); +assert($emitter instanceof Emitter); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$response = $routeMiddleWare->process($request, $handler); +$emitter->emit($response); +``` +Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of +code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). + +So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. + +I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class +should require to function properly. + +* A RequestFactory + We want our Kernel to be able to build the request itself +* An Emitter + Without an Emitter we will not be able to send the response to the client +* RouteMiddleware + To decore the request with the correct handler for the requested route +* RequestHandler + To delegate the request to the correct funtion that creates the response + +As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface +to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our +interface: + +```php +factory::fromGlobals(); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} +``` + +For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: + +```php +routeMiddleware->process($request, $this->handler); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} + +``` + +We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines + +```php +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); +``` + +You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking +at the Whoops output and adding the needed definitions to the dependencies.php file. + +And as always, don't forget to commit your changes. + +[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/14-middleware.md b/14-middleware.md new file mode 100644 index 0000000..f4d3f3b --- /dev/null +++ b/14-middleware.md @@ -0,0 +1,220 @@ +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) + +### Middleware + +In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain +a bit more about what this interface does and why it is used in many applications. + +The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets +passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all +the middlewars to the client/emitter. + +So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the +response after it gets created by our handlers. + +So lets take a look at the middleware and the requesthandler interfaces + +```php +interface MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; +} + +interface RequestHandlerInterface +{ + public function handle(ServerRequestInterface $request): ResponseInterface; +} +``` + +The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a +requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the +response. + +But the middleware could just ignore the handler and produce a response on its own as the interface just requires us +to produce a response. + +A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users +that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the +database. + +In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates +the request with an 'isAuthenticated' attribute. + +If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that +response is not already cached, than we let the handler create the response and store that in the cache for a few +seconds + +```php +interface CacheInterface +{ + public function get(string $key, callable $resolver, int $ttl): mixed; +} +``` + +The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one +defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses +the callable to produce the value and stores it for the time specified in ttl. + +so lets write our caching middleware: + +```php +final class CachingMiddleware implements MiddlewareInterface +{ + public function __construct(private CacheInterface $cache){} + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if ($request->getAttribute('isAuthenticated', false)) { + $key = $request->getUri()->getPath(); + return $this->cache->get($key, fn() => $handler->handle($request), 10); + } + return $handler->handle($request); + } +} +``` + +we can also modify the response after it has been created by our application, for example we could implement a gzip +middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser +know that our application is used to serve dank memes: + +```php +final class DankMemeMiddleware implements MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $response = $handler->handle($request); + return $response->withAddedHeader('Meme', 'Dank'); + } +} +``` + +but for our application we are going to just add two external middlewares: + +* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. +* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware + +```bash +composer require middlewares/trailing-slash +composer require middlewares/whoops +``` + +The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the +application as well as the middleware stack. + +Our desired request -> response flow looks something like this: + + Client + | ^ + v | + Kernel + | ^ + v | + Whoops Middleware + | ^ + v | + TrailingSlash + | ^ + v | + Routing + | ^ + v | + ContainerResolver + | ^ + v | + Controller/Action + +As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every +middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. + +```php +interface Pipeline +{ + public function dispatch(ServerRequestInterface $request): ResponseInterface; +} +``` + +And our implementation looks something like this: + +```php +final class SimplePipeline implements Pipeline +{ + /** + * @param MiddlewareInterface[] $middlewares + * @param RequestHandlerInterface $tip + */ + public function __construct( + private readonly array $middlewares, + private RequestHandlerInterface $tip + ){} + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + $this->tip = new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $next + ){} + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->next); + } + }; + } + } +} +``` + +Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code +that should produce our response. + +In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument +and store that itself as the current tip. + +There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) + +Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline: +```php + Pipeline::class => function ( + RouteDecoratedRequestHandler $tip, + RouteDecorationMiddleware $router, + ) { + $middlewares = require __DIR__ . '/middlewares.php'; + $middlewares[] = $router; + return new SimplePipeline($middlewares, $tip); + }, +``` +And of course a new file called middlewares.php in our config folder: +```php +pipeline->dispatch($request); +} +``` + +Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our +pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a +dependency container, if you want you can do that as well. + +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) diff --git a/README.md b/README.md index ad2da80..af7b810 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,3 @@ -Because this tutorial was so well-received, it inspired me to write a book. The book is a much more up to date version of this tutorial and covers a lot more. Click the link below to check it out (there is also a sample chapter available). +# No Framework +[Start](01-front-controller.md) -### [Professional PHP: Building maintainable and secure applications](http://patricklouys.com/professional-php/) - -![](http://patricklouys.com/img/professional-php-thumb.png) - -The tutorial is still available in it's original form below. - -## Create a PHP application without a framework - -### Introduction - -If you are new to the language, this tutorial is not for you. This tutorial is aimed at people who have grasped the basics of PHP and know a little bit about object-oriented programming. - -You should have at least heard of [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29). If you are not familiar with it, now would be a good time to familiarize yourself with the principles before you start with the tutorial. - -I saw a lot of people coming into the Stack Overflow PHP chatroom and asking if framework X is any good. Most of the time the answer was that they should just use PHP and not a framework to build their application. But many are overwhelmed by this and don't know where to start. - -So my goal with this is to provide an easy resource that people can be pointed to. In most cases a framework does not make sense and writing an application from scratch with the help of some third party packages is much, much easier than some people think. - -**This tutorial was written for PHP 7.0 or newer versions.** If you are using an older version, please upgrade it before you start. I recommend that you use the [current stable version](http://php.net/downloads.php). - -So let's get started right away with the [first part](01-front-controller.md). - -### Parts - -1. [Front Controller](01-front-controller.md) -2. [Composer](02-composer.md) -3. [Error Handler](03-error-handler.md) -4. [HTTP](04-http.md) -5. [Router](05-router.md) -6. [Dispatching to a Class](06-dispatching-to-a-class.md) -7. [Inversion of Control](07-inversion-of-control.md) -8. [Dependency Injector](08-dependency-injector.md) -9. [Templating](09-templating.md) -10. [Dynamic Pages](10-dynamic-pages.md) -11. [Page Menu](11-page-menu.md) -12. [Frontend](12-frontend.md) diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..0b30662 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,22 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +Vagrant.configure("2") do |config| + config.vm.box = "archlinux/archlinux" + config.vm.network "forwarded_port", guest: 1234, host: 1234 + config.vm.network "forwarded_port", guest: 22, host: 2200, id: 'ssh' + config.vm.synced_folder "./app", "/home/vagrant/app" + config.ssh.username = 'vagrant' + config.ssh.password = 'vagrant' + config.vm.provision "shell", inline: <<-SHELL + pacman -Syu --noconfirm + pacman -S --noconfirm php php-sqlite php-intl php-sodium php-apcu composer xdebug vim + echo '127.0.0.1 localhost' >> /etc/hosts + echo -e 'extension=pdo_sqlite\nextenstion=sqlite3\n' >> /etc/php/conf.d/tutorial.ini + echo -e 'extension=apcu\nzend_extension=opcache\n' >> /etc/php/conf.d/tutorial.ini + echo -e 'zend_extension=xdebug\nxdebug.client_host=10.0.2.2\n' >> /etc/php/conf.d/tutorial.ini + echo -e 'xdebug.client_port=9003\nxdebug.mode=debug\n' >> /etc/php/conf.d/tutorial.ini + echo -e 'zend.assertions=1\n' >> /etc/php/conf.d/tutorial.ini + + SHELL +end diff --git a/app/public/favicon.ico b/app/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/app/src/.gitkeep b/app/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/src/Http/AddRoute.php b/app/src/Http/AddRoute.php new file mode 100644 index 0000000..0400347 --- /dev/null +++ b/app/src/Http/AddRoute.php @@ -0,0 +1,17 @@ +|class-string|callable $handler + */ + public function addRoute( + string $method, + string $path, + array|string|callable $handler, + ): self; +} \ No newline at end of file diff --git a/app/src/Http/RouteDecorationMiddleware.php b/app/src/Http/RouteDecorationMiddleware.php new file mode 100644 index 0000000..ff2c846 --- /dev/null +++ b/app/src/Http/RouteDecorationMiddleware.php @@ -0,0 +1,97 @@ + $routes + */ + public function __construct( + private readonly ResponseFactoryInterface $responseFactory, + private readonly string $routeAttributeName = '__route_handler', + private array $routes = [], + private Dispatcher|null $dispatcher = null, + ) + { + } + + /** + * @throws MethodNotAllowed + * @throws NotFound + */ + private function decorateRequest(ServerRequestInterface $request): ServerRequestInterface + { + $this->dispatcher ??= $this->createDispatcher(); + $routeInfo = $this->dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath() + ); + + if ($routeInfo[0] === Dispatcher::NOT_FOUND) { + throw new NotFound; + } + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + + return $request->withAttribute($this->routeAttributeName, $routeInfo[1]); + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + + return $handler->handle($request); + } + + private function createDispatcher(): Dispatcher + { + return simpleDispatcher(function (RouteCollector $r) { + foreach ($this->routes as $route) { + $r->addRoute($route[0], $route[1], $route[2]); + } + }); + } + + /** + * @inheritDoc + */ + public function addRoute(string $method, string $path, array|string|callable $handler,): AddRoute + { + $this->routes[] = [$method, $path, $handler]; + return $this; + } +} diff --git a/app/src/Kernel.php b/app/src/Kernel.php new file mode 100644 index 0000000..85c0ee8 --- /dev/null +++ b/app/src/Kernel.php @@ -0,0 +1,63 @@ +routeMiddleware->process($request, $this->handler); + } + + public function run(ServerRequestInterface |null $request = null): void + { + $request ??= $this->createRequest(); + $response = $this->handle($request); + $this->emitter->emit($response); + } + + private function createRequest(): ServerRequestInterface + { + try { + $request = $this->container->get(ServerRequestInterface::class); + assert($request instanceof ServerRequestInterface); + return $request; + } catch (Throwable $t) { + throw new InternalServerError( + 'could not get Request from container, please configure the container ' . + 'in order to use run() wihtout a request', + $t->getCode(), + $t, + ); + } + } + + public function addRoute( + string $method, + string $path, + array|string|callable $handler): AddRoute + { + $this->routeMiddleware->addRoute($method, $path, $handler); + return $this; + } +} diff --git a/implementation/01-front-controller/public/index.php b/implementation/01-front-controller/public/index.php new file mode 100644 index 0000000..43d37a2 --- /dev/null +++ b/implementation/01-front-controller/public/index.php @@ -0,0 +1,5 @@ +=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "phpstan/phpstan", + "version": "1.4.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.4.10" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-14T10:25:45+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/02-composer/public/index.php b/implementation/02-composer/public/index.php new file mode 100644 index 0000000..43d37a2 --- /dev/null +++ b/implementation/02-composer/public/index.php @@ -0,0 +1,5 @@ +=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "phpstan/phpstan", + "version": "1.4.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.4.10" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-14T10:25:45+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/03-error-handler/public/index.php b/implementation/03-error-handler/public/index.php new file mode 100644 index 0000000..43d37a2 --- /dev/null +++ b/implementation/03-error-handler/public/index.php @@ -0,0 +1,5 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (\Throwable $e) { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +throw new \Exception("Ooooopsie"); diff --git a/implementation/04-dev-helpers/.php-cs-fixer.php b/implementation/04-dev-helpers/.php-cs-fixer.php new file mode 100644 index 0000000..3db1195 --- /dev/null +++ b/implementation/04-dev-helpers/.php-cs-fixer.php @@ -0,0 +1,41 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'linebreak_after_opening_tag' => true, + 'native_constant_invocation' => true, + 'native_function_invocation' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha', + 'imports_order' => [ + 'const', + 'class', + 'function', + ], + ], + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__ . '/src') + ); \ No newline at end of file diff --git a/implementation/04-dev-helpers/composer.json b/implementation/04-dev-helpers/composer.json new file mode 100644 index 0000000..995d677 --- /dev/null +++ b/implementation/04-dev-helpers/composer.json @@ -0,0 +1,29 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "scripts": { + "serve": "php -S localhost:1234 -t ./public", + "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", + "phpstan": "./vendor/bin/phpstan analyze", + "style": "./vendor/bin/php-cs-fixer fix" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "php-cs-fixer/shim": "^3.7", + "symfony/var-dumper": "^6.0" + } +} diff --git a/implementation/04-dev-helpers/composer.lock b/implementation/04-dev-helpers/composer.lock new file mode 100644 index 0000000..8ac2710 --- /dev/null +++ b/implementation/04-dev-helpers/composer.lock @@ -0,0 +1,420 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "e956f9f7a53de7c1c149a093926ab371", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "php-cs-fixer/shim", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" + }, + "time": "2022-03-07T17:02:59+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.4.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.4.10" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-14T10:25:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/04-dev-helpers/phpstan.neon b/implementation/04-dev-helpers/phpstan.neon new file mode 100644 index 0000000..c2f33f3 --- /dev/null +++ b/implementation/04-dev-helpers/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: 9 + paths: + - src \ No newline at end of file diff --git a/implementation/04-dev-helpers/public/index.php b/implementation/04-dev-helpers/public/index.php new file mode 100644 index 0000000..32f5eb3 --- /dev/null +++ b/implementation/04-dev-helpers/public/index.php @@ -0,0 +1,5 @@ +pushHandler(new PrettyPageHandler()); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +throw new Exception("Ooooopsie"); diff --git a/implementation/05-http/.php-cs-fixer.php b/implementation/05-http/.php-cs-fixer.php new file mode 100644 index 0000000..3db1195 --- /dev/null +++ b/implementation/05-http/.php-cs-fixer.php @@ -0,0 +1,41 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'linebreak_after_opening_tag' => true, + 'native_constant_invocation' => true, + 'native_function_invocation' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha', + 'imports_order' => [ + 'const', + 'class', + 'function', + ], + ], + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__ . '/src') + ); \ No newline at end of file diff --git a/implementation/05-http/composer.json b/implementation/05-http/composer.json new file mode 100644 index 0000000..a576724 --- /dev/null +++ b/implementation/05-http/composer.json @@ -0,0 +1,30 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "scripts": { + "serve": "php -S localhost:1234 -t ./public", + "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", + "phpstan": "./vendor/bin/phpstan analyze", + "style": "./vendor/bin/php-cs-fixer fix" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "php-cs-fixer/shim": "^3.7", + "symfony/var-dumper": "^6.0" + } +} diff --git a/implementation/05-http/composer.lock b/implementation/05-http/composer.lock new file mode 100644 index 0000000..c8f686e --- /dev/null +++ b/implementation/05-http/composer.lock @@ -0,0 +1,627 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "8f46f82d7ff0a4180389107e37ae5ed0", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "php-cs-fixer/shim", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" + }, + "time": "2022-03-07T17:02:59+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.4.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.4.10" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-14T10:25:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/05-http/phpstan-baseline.neon b/implementation/05-http/phpstan-baseline.neon new file mode 100644 index 0000000..e69de29 diff --git a/implementation/05-http/phpstan.neon b/implementation/05-http/phpstan.neon new file mode 100644 index 0000000..c2f33f3 --- /dev/null +++ b/implementation/05-http/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: 9 + paths: + - src \ No newline at end of file diff --git a/implementation/05-http/public/index.php b/implementation/05-http/public/index.php new file mode 100644 index 0000000..43d37a2 --- /dev/null +++ b/implementation/05-http/public/index.php @@ -0,0 +1,5 @@ +pushHandler(new PrettyPageHandler()); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$request = ServerRequestFactory::fromGlobals(); +$response = new Response(); +$response->getBody()->write('Hello World!'); + +dd($response); + +/** @var string $name */ +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/06-router/.php-cs-fixer.php b/implementation/06-router/.php-cs-fixer.php new file mode 100644 index 0000000..6b8a091 --- /dev/null +++ b/implementation/06-router/.php-cs-fixer.php @@ -0,0 +1,44 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'linebreak_after_opening_tag' => true, + 'native_constant_invocation' => true, + 'native_function_invocation' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha', + 'imports_order' => [ + 'const', + 'class', + 'function', + ], + ], + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config', + ]) + ); \ No newline at end of file diff --git a/implementation/06-router/composer.json b/implementation/06-router/composer.json new file mode 100644 index 0000000..c046545 --- /dev/null +++ b/implementation/06-router/composer.json @@ -0,0 +1,31 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "scripts": { + "serve": "php -S localhost:1234 -t ./public", + "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", + "phpstan": "./vendor/bin/phpstan analyze", + "style": "./vendor/bin/php-cs-fixer fix" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "php-cs-fixer/shim": "^3.7", + "symfony/var-dumper": "^6.0" + } +} diff --git a/implementation/06-router/composer.lock b/implementation/06-router/composer.lock new file mode 100644 index 0000000..91a0551 --- /dev/null +++ b/implementation/06-router/composer.lock @@ -0,0 +1,677 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "d76b01ed13a5b0b42ce69a745090f7d9", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "php-cs-fixer/shim", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" + }, + "time": "2022-03-07T17:02:59+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.4.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.4.10" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-14T10:25:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/06-router/config/routes.php b/implementation/06-router/config/routes.php new file mode 100644 index 0000000..baea1f0 --- /dev/null +++ b/implementation/06-router/config/routes.php @@ -0,0 +1,17 @@ +addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response())->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new \Laminas\Diactoros\Response())->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}; diff --git a/implementation/06-router/phpstan-baseline.neon b/implementation/06-router/phpstan-baseline.neon new file mode 100644 index 0000000..e69de29 diff --git a/implementation/06-router/phpstan.neon b/implementation/06-router/phpstan.neon new file mode 100644 index 0000000..c2f33f3 --- /dev/null +++ b/implementation/06-router/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: 9 + paths: + - src \ No newline at end of file diff --git a/implementation/06-router/public/index.php b/implementation/06-router/public/index.php new file mode 100644 index 0000000..43d37a2 --- /dev/null +++ b/implementation/06-router/public/index.php @@ -0,0 +1,5 @@ +pushHandler(new PrettyPageHandler()); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$request = ServerRequestFactory::fromGlobals(); +$response = new Response(); + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +switch ($routeInfo[0]) { + case Dispatcher::METHOD_NOT_ALLOWED: + $response = (new Response())->withStatus(405); + $response->getBody()->write('Method not allowed'); + $response = $response->withStatus(405); + break; + case Dispatcher::FOUND: + $handler = $routeInfo[1]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + /** @var ResponseInterface $response */ + $response = call_user_func($handler, $request); + break; + case Dispatcher::NOT_FOUND: + default: + $response = (new Response())->withStatus(404); + $response->getBody()->write('Not Found!'); + break; +} + +/** @var string $name */ +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/07-dispatching-to-class/.php-cs-fixer.php b/implementation/07-dispatching-to-class/.php-cs-fixer.php new file mode 100644 index 0000000..6b8a091 --- /dev/null +++ b/implementation/07-dispatching-to-class/.php-cs-fixer.php @@ -0,0 +1,44 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'linebreak_after_opening_tag' => true, + 'native_constant_invocation' => true, + 'native_function_invocation' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha', + 'imports_order' => [ + 'const', + 'class', + 'function', + ], + ], + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config', + ]) + ); \ No newline at end of file diff --git a/implementation/07-dispatching-to-class/composer.json b/implementation/07-dispatching-to-class/composer.json new file mode 100644 index 0000000..53f35e7 --- /dev/null +++ b/implementation/07-dispatching-to-class/composer.json @@ -0,0 +1,32 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "scripts": { + "serve": "php -S localhost:1234 -t ./public", + "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", + "phpstan": "./vendor/bin/phpstan analyze", + "style": "./vendor/bin/php-cs-fixer fix" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "php-cs-fixer/shim": "^3.7", + "symfony/var-dumper": "^6.0" + } +} diff --git a/implementation/07-dispatching-to-class/composer.lock b/implementation/07-dispatching-to-class/composer.lock new file mode 100644 index 0000000..f2fa011 --- /dev/null +++ b/implementation/07-dispatching-to-class/composer.lock @@ -0,0 +1,734 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "b84022e8ec4df1e5ee958e6cbfb2307c", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "php-cs-fixer/shim", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" + }, + "time": "2022-03-07T17:02:59+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.4.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.4.10" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-14T10:25:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/07-dispatching-to-class/config/routes.php b/implementation/07-dispatching-to-class/config/routes.php new file mode 100644 index 0000000..af63e42 --- /dev/null +++ b/implementation/07-dispatching-to-class/config/routes.php @@ -0,0 +1,8 @@ +addRoute('GET', '/hello[/{name}]', [\Lubian\NoFramework\Action\Hello::class, 'handle']); + $r->addRoute('GET', '/another-route', [\Lubian\NoFramework\Action\Another::class, 'handle']); +}; diff --git a/implementation/07-dispatching-to-class/phpstan-baseline.neon b/implementation/07-dispatching-to-class/phpstan-baseline.neon new file mode 100644 index 0000000..e69de29 diff --git a/implementation/07-dispatching-to-class/phpstan.neon b/implementation/07-dispatching-to-class/phpstan.neon new file mode 100644 index 0000000..c2f33f3 --- /dev/null +++ b/implementation/07-dispatching-to-class/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: 9 + paths: + - src \ No newline at end of file diff --git a/implementation/07-dispatching-to-class/public/index.php b/implementation/07-dispatching-to-class/public/index.php new file mode 100644 index 0000000..43d37a2 --- /dev/null +++ b/implementation/07-dispatching-to-class/public/index.php @@ -0,0 +1,5 @@ +withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + } +} diff --git a/implementation/07-dispatching-to-class/src/Action/Hello.php b/implementation/07-dispatching-to-class/src/Action/Hello.php new file mode 100644 index 0000000..b516c1a --- /dev/null +++ b/implementation/07-dispatching-to-class/src/Action/Hello.php @@ -0,0 +1,21 @@ +getAttribute('name', 'Stranger'); + $response = (new Response())->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + } +} diff --git a/implementation/07-dispatching-to-class/src/Action/InternalServerError.php b/implementation/07-dispatching-to-class/src/Action/InternalServerError.php new file mode 100644 index 0000000..5f2aa5b --- /dev/null +++ b/implementation/07-dispatching-to-class/src/Action/InternalServerError.php @@ -0,0 +1,20 @@ +withStatus(500); + $response->getBody()->write('Internal Server Error.'); + return $response; + } +} diff --git a/implementation/07-dispatching-to-class/src/Action/NotAllowed.php b/implementation/07-dispatching-to-class/src/Action/NotAllowed.php new file mode 100644 index 0000000..799f4e3 --- /dev/null +++ b/implementation/07-dispatching-to-class/src/Action/NotAllowed.php @@ -0,0 +1,20 @@ +withStatus(405); + $response->getBody()->write('Method Not Allowed'); + return $response; + } +} diff --git a/implementation/07-dispatching-to-class/src/Action/NotFound.php b/implementation/07-dispatching-to-class/src/Action/NotFound.php new file mode 100644 index 0000000..0a88dc5 --- /dev/null +++ b/implementation/07-dispatching-to-class/src/Action/NotFound.php @@ -0,0 +1,20 @@ +withStatus(404); + $response->getBody()->write('Page not found'); + return $response; + } +} diff --git a/implementation/07-dispatching-to-class/src/Bootstrap.php b/implementation/07-dispatching-to-class/src/Bootstrap.php new file mode 100644 index 0000000..c13c6fb --- /dev/null +++ b/implementation/07-dispatching-to-class/src/Bootstrap.php @@ -0,0 +1,102 @@ +pushHandler(new PrettyPageHandler()); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$request = ServerRequestFactory::fromGlobals(); +$response = new Response(); + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +try { + switch ($routeInfo[0]) { + case Dispatcher::METHOD_NOT_ALLOWED: + throw new NotAllowedException(); + case Dispatcher::FOUND: + /** @var RequestHandlerInterface $handler */ + $handler = new $routeInfo[1][0](); + $method = $routeInfo[1][1]; + if (!$handler instanceof RequestHandlerInterface) { + throw new InternalServerErrorException(); + } + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->$method($request); + break; + case Dispatcher::NOT_FOUND: + default: + throw new NotFoundException(); + } +} catch (NotAllowedException) { + $response = (new NotAllowed())->handle($request); +} catch (NotFoundException) { + $response = (new NotFound())->handle($request); +} catch (Exception) { + $response = (new InternalServerError())->handle($request); +} + +/** @var string $name */ +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/07-dispatching-to-class/src/Exception/InternalServerErrorException.php b/implementation/07-dispatching-to-class/src/Exception/InternalServerErrorException.php new file mode 100644 index 0000000..aa3fddd --- /dev/null +++ b/implementation/07-dispatching-to-class/src/Exception/InternalServerErrorException.php @@ -0,0 +1,11 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'linebreak_after_opening_tag' => true, + 'native_constant_invocation' => true, + 'native_function_invocation' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha', + 'imports_order' => [ + 'const', + 'class', + 'function', + ], + ], + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config', + ]) + ); \ No newline at end of file diff --git a/implementation/08-inversion-of-control/composer.json b/implementation/08-inversion-of-control/composer.json new file mode 100644 index 0000000..53f35e7 --- /dev/null +++ b/implementation/08-inversion-of-control/composer.json @@ -0,0 +1,32 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "scripts": { + "serve": "php -S localhost:1234 -t ./public", + "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", + "phpstan": "./vendor/bin/phpstan analyze", + "style": "./vendor/bin/php-cs-fixer fix" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "php-cs-fixer/shim": "^3.7", + "symfony/var-dumper": "^6.0" + } +} diff --git a/implementation/08-inversion-of-control/composer.lock b/implementation/08-inversion-of-control/composer.lock new file mode 100644 index 0000000..f2fa011 --- /dev/null +++ b/implementation/08-inversion-of-control/composer.lock @@ -0,0 +1,734 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "b84022e8ec4df1e5ee958e6cbfb2307c", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "php-cs-fixer/shim", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" + }, + "time": "2022-03-07T17:02:59+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.4.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.4.10" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-14T10:25:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/08-inversion-of-control/config/routes.php b/implementation/08-inversion-of-control/config/routes.php new file mode 100644 index 0000000..af63e42 --- /dev/null +++ b/implementation/08-inversion-of-control/config/routes.php @@ -0,0 +1,8 @@ +addRoute('GET', '/hello[/{name}]', [\Lubian\NoFramework\Action\Hello::class, 'handle']); + $r->addRoute('GET', '/another-route', [\Lubian\NoFramework\Action\Another::class, 'handle']); +}; diff --git a/implementation/08-inversion-of-control/phpstan-baseline.neon b/implementation/08-inversion-of-control/phpstan-baseline.neon new file mode 100644 index 0000000..e69de29 diff --git a/implementation/08-inversion-of-control/phpstan.neon b/implementation/08-inversion-of-control/phpstan.neon new file mode 100644 index 0000000..c2f33f3 --- /dev/null +++ b/implementation/08-inversion-of-control/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: 9 + paths: + - src \ No newline at end of file diff --git a/implementation/08-inversion-of-control/public/index.php b/implementation/08-inversion-of-control/public/index.php new file mode 100644 index 0000000..43d37a2 --- /dev/null +++ b/implementation/08-inversion-of-control/public/index.php @@ -0,0 +1,5 @@ +response->getBody()->write('This works too!'); + return $this->response; + } +} diff --git a/implementation/08-inversion-of-control/src/Action/Hello.php b/implementation/08-inversion-of-control/src/Action/Hello.php new file mode 100644 index 0000000..b45021e --- /dev/null +++ b/implementation/08-inversion-of-control/src/Action/Hello.php @@ -0,0 +1,20 @@ +getAttribute('name', 'Stranger'); + $this->response->getBody()->write('Hello ' . $name . '!'); + return $this->response; + } +} diff --git a/implementation/08-inversion-of-control/src/Action/InternalServerError.php b/implementation/08-inversion-of-control/src/Action/InternalServerError.php new file mode 100644 index 0000000..d1acbb8 --- /dev/null +++ b/implementation/08-inversion-of-control/src/Action/InternalServerError.php @@ -0,0 +1,19 @@ +response->getBody()->write('Internal Server Error.'); + return $this->response->withStatus(500); + } +} diff --git a/implementation/08-inversion-of-control/src/Action/NotAllowed.php b/implementation/08-inversion-of-control/src/Action/NotAllowed.php new file mode 100644 index 0000000..83abb60 --- /dev/null +++ b/implementation/08-inversion-of-control/src/Action/NotAllowed.php @@ -0,0 +1,19 @@ +response->getBody()->write('Method Not Allowed'); + return $this->response->withStatus(405); + } +} diff --git a/implementation/08-inversion-of-control/src/Action/NotFound.php b/implementation/08-inversion-of-control/src/Action/NotFound.php new file mode 100644 index 0000000..ceee42f --- /dev/null +++ b/implementation/08-inversion-of-control/src/Action/NotFound.php @@ -0,0 +1,19 @@ +response->getBody()->write('Page not found'); + return $this->response->withStatus(404); + } +} diff --git a/implementation/08-inversion-of-control/src/Bootstrap.php b/implementation/08-inversion-of-control/src/Bootstrap.php new file mode 100644 index 0000000..c7ec6e8 --- /dev/null +++ b/implementation/08-inversion-of-control/src/Bootstrap.php @@ -0,0 +1,102 @@ +pushHandler(new PrettyPageHandler()); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$request = ServerRequestFactory::fromGlobals(); +$response = new Response(); + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +try { + switch ($routeInfo[0]) { + case Dispatcher::METHOD_NOT_ALLOWED: + throw new NotAllowedException(); + case Dispatcher::FOUND: + /** @var RequestHandlerInterface $handler */ + $handler = new $routeInfo[1][0]($response); + $method = $routeInfo[1][1]; + if (!$handler instanceof RequestHandlerInterface) { + throw new InternalServerErrorException(); + } + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->$method($request); + break; + case Dispatcher::NOT_FOUND: + default: + throw new NotFoundException(); + } +} catch (NotAllowedException) { + $response = (new NotAllowed($response))->handle($request); +} catch (NotFoundException) { + $response = (new NotFound($response))->handle($request); +} catch (Exception) { + $response = (new InternalServerError($response))->handle($request); +} + +/** @var string $name */ +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/08-inversion-of-control/src/Exception/InternalServerErrorException.php b/implementation/08-inversion-of-control/src/Exception/InternalServerErrorException.php new file mode 100644 index 0000000..aa3fddd --- /dev/null +++ b/implementation/08-inversion-of-control/src/Exception/InternalServerErrorException.php @@ -0,0 +1,11 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'linebreak_after_opening_tag' => true, + 'native_constant_invocation' => true, + 'native_function_invocation' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha', + 'imports_order' => [ + 'const', + 'class', + 'function', + ], + ], + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config', + ]) + ); \ No newline at end of file diff --git a/implementation/09-dependency-injector/composer.json b/implementation/09-dependency-injector/composer.json new file mode 100644 index 0000000..a38d437 --- /dev/null +++ b/implementation/09-dependency-injector/composer.json @@ -0,0 +1,34 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "scripts": { + "serve": "php -S localhost:1234 -t ./public", + "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "style": "./vendor/bin/php-cs-fixer fix" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "php-cs-fixer/shim": "^3.7", + "symfony/var-dumper": "^6.0" + } +} diff --git a/implementation/09-dependency-injector/composer.lock b/implementation/09-dependency-injector/composer.lock new file mode 100644 index 0000000..cb0aa21 --- /dev/null +++ b/implementation/09-dependency-injector/composer.lock @@ -0,0 +1,1020 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "176033a9d3cd7179cb7bb9608fa24210", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "php-cs-fixer/shim", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" + }, + "time": "2022-03-07T17:02:59+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.4.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.4.10" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-14T10:25:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/09-dependency-injector/config/dependencies.php b/implementation/09-dependency-injector/config/dependencies.php new file mode 100644 index 0000000..ac1d962 --- /dev/null +++ b/implementation/09-dependency-injector/config/dependencies.php @@ -0,0 +1,11 @@ +addDefinitions([ + \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), +]); + +return $builder->build(); diff --git a/implementation/09-dependency-injector/config/routes.php b/implementation/09-dependency-injector/config/routes.php new file mode 100644 index 0000000..af63e42 --- /dev/null +++ b/implementation/09-dependency-injector/config/routes.php @@ -0,0 +1,8 @@ +addRoute('GET', '/hello[/{name}]', [\Lubian\NoFramework\Action\Hello::class, 'handle']); + $r->addRoute('GET', '/another-route', [\Lubian\NoFramework\Action\Another::class, 'handle']); +}; diff --git a/implementation/09-dependency-injector/phpstan-baseline.neon b/implementation/09-dependency-injector/phpstan-baseline.neon new file mode 100644 index 0000000..fe0b762 --- /dev/null +++ b/implementation/09-dependency-injector/phpstan-baseline.neon @@ -0,0 +1,7 @@ +parameters: + ignoreErrors: + - + message: "#^Cannot call method handle\\(\\) on mixed\\.$#" + count: 3 + path: src/Bootstrap.php + diff --git a/implementation/09-dependency-injector/phpstan.neon b/implementation/09-dependency-injector/phpstan.neon new file mode 100644 index 0000000..28914db --- /dev/null +++ b/implementation/09-dependency-injector/phpstan.neon @@ -0,0 +1,7 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src \ No newline at end of file diff --git a/implementation/09-dependency-injector/public/index.php b/implementation/09-dependency-injector/public/index.php new file mode 100644 index 0000000..43d37a2 --- /dev/null +++ b/implementation/09-dependency-injector/public/index.php @@ -0,0 +1,5 @@ +response->getBody()->write('This works too!'); + return $this->response; + } +} diff --git a/implementation/09-dependency-injector/src/Action/Hello.php b/implementation/09-dependency-injector/src/Action/Hello.php new file mode 100644 index 0000000..b45021e --- /dev/null +++ b/implementation/09-dependency-injector/src/Action/Hello.php @@ -0,0 +1,20 @@ +getAttribute('name', 'Stranger'); + $this->response->getBody()->write('Hello ' . $name . '!'); + return $this->response; + } +} diff --git a/implementation/09-dependency-injector/src/Action/InternalServerError.php b/implementation/09-dependency-injector/src/Action/InternalServerError.php new file mode 100644 index 0000000..d1acbb8 --- /dev/null +++ b/implementation/09-dependency-injector/src/Action/InternalServerError.php @@ -0,0 +1,19 @@ +response->getBody()->write('Internal Server Error.'); + return $this->response->withStatus(500); + } +} diff --git a/implementation/09-dependency-injector/src/Action/NotAllowed.php b/implementation/09-dependency-injector/src/Action/NotAllowed.php new file mode 100644 index 0000000..83abb60 --- /dev/null +++ b/implementation/09-dependency-injector/src/Action/NotAllowed.php @@ -0,0 +1,19 @@ +response->getBody()->write('Method Not Allowed'); + return $this->response->withStatus(405); + } +} diff --git a/implementation/09-dependency-injector/src/Action/NotFound.php b/implementation/09-dependency-injector/src/Action/NotFound.php new file mode 100644 index 0000000..ceee42f --- /dev/null +++ b/implementation/09-dependency-injector/src/Action/NotFound.php @@ -0,0 +1,19 @@ +response->getBody()->write('Page not found'); + return $this->response->withStatus(404); + } +} diff --git a/implementation/09-dependency-injector/src/Bootstrap.php b/implementation/09-dependency-injector/src/Bootstrap.php new file mode 100644 index 0000000..a059b66 --- /dev/null +++ b/implementation/09-dependency-injector/src/Bootstrap.php @@ -0,0 +1,106 @@ +pushHandler(new PrettyPageHandler()); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +/** @var ContainerInterface $container */ +$container = require __DIR__ . '/../config/dependencies.php'; +/** @var ServerRequestInterface $request */ +$request = $container->get(ServerRequestInterface::class); + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +try { + switch ($routeInfo[0]) { + case Dispatcher::METHOD_NOT_ALLOWED: + throw new NotAllowedException(); + case Dispatcher::FOUND: + /** @var RequestHandlerInterface $handler */ + $handler = $container->get($routeInfo[1][0]); + $method = $routeInfo[1][1]; + if (!$handler instanceof RequestHandlerInterface) { + throw new InternalServerErrorException(); + } + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->$method($request); + break; + case Dispatcher::NOT_FOUND: + default: + throw new NotFoundException(); + } +} catch (NotAllowedException) { + $response = $container->get(NotAllowed::class)->handle($request); +} catch (NotFoundException) { + $response = $container->get(NotFound::class)->handle($request); +} catch (Exception) { + $response = $container->get(InternalServerError::class)->handle($request); +} + +/** @var string $name */ +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/09-dependency-injector/src/Exception/InternalServerErrorException.php b/implementation/09-dependency-injector/src/Exception/InternalServerErrorException.php new file mode 100644 index 0000000..aa3fddd --- /dev/null +++ b/implementation/09-dependency-injector/src/Exception/InternalServerErrorException.php @@ -0,0 +1,11 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'linebreak_after_opening_tag' => true, + 'native_constant_invocation' => true, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha', + 'imports_order' => [ + 'const', + 'class', + 'function', + ], + ], + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config', + ]) + ); \ No newline at end of file diff --git a/implementation/10-invoker/composer.json b/implementation/10-invoker/composer.json new file mode 100644 index 0000000..3c17ae5 --- /dev/null +++ b/implementation/10-invoker/composer.json @@ -0,0 +1,32 @@ +{ + "name": "lubian/no-framework", + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0" + }, + "scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "style": "./vendor/bin/php-cs-fixer fix" + } +} diff --git a/implementation/10-invoker/composer.lock b/implementation/10-invoker/composer.lock new file mode 100644 index 0000000..d300ec9 --- /dev/null +++ b/implementation/10-invoker/composer.lock @@ -0,0 +1,1020 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "c8641669dc93f9bfa8f95f20a8598451", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.4.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.4.10" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-14T10:25:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/10-invoker/config/dependencies.php b/implementation/10-invoker/config/dependencies.php new file mode 100644 index 0000000..3708e0d --- /dev/null +++ b/implementation/10-invoker/config/dependencies.php @@ -0,0 +1,12 @@ +addDefinitions([ + \Psr\Http\Message\ResponseInterface::class => fn () => new \Laminas\Diactoros\Response(), + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), + \Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), +]); + +return $builder->build(); diff --git a/implementation/10-invoker/config/routes.php b/implementation/10-invoker/config/routes.php new file mode 100644 index 0000000..a60931c --- /dev/null +++ b/implementation/10-invoker/config/routes.php @@ -0,0 +1,18 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); + $r->addRoute( + 'GET', + '/', + fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello') + ); +}; diff --git a/implementation/10-invoker/phpstan.neon b/implementation/10-invoker/phpstan.neon new file mode 100644 index 0000000..c6ce9bd --- /dev/null +++ b/implementation/10-invoker/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: 9 + paths: + - src + - config \ No newline at end of file diff --git a/implementation/10-invoker/public/favicon.ico b/implementation/10-invoker/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/10-invoker/public/index.php b/implementation/10-invoker/public/index.php new file mode 100644 index 0000000..db23804 --- /dev/null +++ b/implementation/10-invoker/public/index.php @@ -0,0 +1,6 @@ +withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + $nowString = $now->get()->format('H:i:s'); + $response->getBody()->write(' The Time is ' . $nowString); + return $response; + } +} diff --git a/implementation/10-invoker/src/Action/Other.php b/implementation/10-invoker/src/Action/Other.php new file mode 100644 index 0000000..3563983 --- /dev/null +++ b/implementation/10-invoker/src/Action/Other.php @@ -0,0 +1,20 @@ +response->withStatus(200); + $response->getBody()->write('This works too'); + return $response; + } +} diff --git a/implementation/10-invoker/src/Bootstrap.php b/implementation/10-invoker/src/Bootstrap.php new file mode 100644 index 0000000..d3bfc96 --- /dev/null +++ b/implementation/10-invoker/src/Bootstrap.php @@ -0,0 +1,110 @@ +pushHandler(new PrettyPageHandler()); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log("ERROR: " . $e->getMessage(), $e->getCode()); + echo 'AN ERROR HAPPENED!!!'; + }); +} + +$whoops->register(); + +/** @var ContainerInterface $container */ +$container = require __DIR__ . '/../config/dependencies.php'; + +/** @var InvokerInterface $invoker */ +$invoker = $container->get(InvokerInterface::class); + +/** @var ServerRequestInterface $request */ +$request = $container->get(ServerRequestInterface::class); + +/** @var ResponseInterface $response */ +$response = $container->get(ResponseInterface::class); + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri()->getPath()); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + $handler = $routeInfo[1]; + $args = $routeInfo[2]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $args['request'] = $request; + $response = $container->call($handler, $args); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed(); + case Dispatcher::NOT_FOUND: + default: + throw new NotFound(); + } +} catch (NotFound) { + $response = $response->withStatus(404); + $response->getBody()->write('Not Found'); +} catch (MethodNotAllowed) { + $response = $response->withStatus(405); + $response->getBody()->write('Method not Allowed'); +} catch (Exception $e) { + throw new InternalServerError($e->getMessage(), $e->getCode(), $e); +} + + + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase(), +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/10-invoker/src/Exception/InternalServerError.php b/implementation/10-invoker/src/Exception/InternalServerError.php new file mode 100644 index 0000000..a7b627b --- /dev/null +++ b/implementation/10-invoker/src/Exception/InternalServerError.php @@ -0,0 +1,11 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config' + ]) + ); \ No newline at end of file diff --git a/implementation/11-templating/.phpcs.xml.dist b/implementation/11-templating/.phpcs.xml.dist new file mode 100644 index 0000000..3b433f6 --- /dev/null +++ b/implementation/11-templating/.phpcs.xml.dist @@ -0,0 +1,9 @@ + + + + + src + config + + + \ No newline at end of file diff --git a/implementation/11-templating/composer.json b/implementation/11-templating/composer.json new file mode 100644 index 0000000..2c61bdd --- /dev/null +++ b/implementation/11-templating/composer.json @@ -0,0 +1,46 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3", + "mustache/mustache": "^2.14" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require-dev": { + "phpstan/phpstan": "^1.5", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0", + "squizlabs/php_codesniffer": "^3.6", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.1", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "mnapoli/hard-mode": "^0.3.0" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + } +} diff --git a/implementation/11-templating/composer.lock b/implementation/11-templating/composer.lock new file mode 100644 index 0000000..37ee56e --- /dev/null +++ b/implementation/11-templating/composer.lock @@ -0,0 +1,1550 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "e237b9a5b4210f235b1609a7ce3e065a", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "doctrine/coding-standard", + "version": "8.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/coding-standard.git", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Müller", + "email": "st.mueller@dzh-online.de" + } + ], + "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "doctrine", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/doctrine/coding-standard/issues", + "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" + }, + "time": "2021-04-03T10:54:55+00:00" + }, + { + "name": "mnapoli/hard-mode", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/hard-mode.git", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", + "shasum": "" + }, + "require": { + "doctrine/coding-standard": "^8.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Hard mode for PHP", + "support": { + "issues": "https://github.com/mnapoli/hard-mode/issues", + "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" + }, + "time": "2020-10-12T07:54:37+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.5.0" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-24T18:18:00+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^1.2.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" + }, + "time": "2021-11-18T09:30:29+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/11-templating/config/dependencies.php b/implementation/11-templating/config/dependencies.php new file mode 100644 index 0000000..c3e65a8 --- /dev/null +++ b/implementation/11-templating/config/dependencies.php @@ -0,0 +1,32 @@ +addDefinitions([ + Settings::class => fn () => require __DIR__ . '/settings.php', + ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + Mustache_Loader_FilesystemLoader::class => function (Settings $s) { + return new Mustache_Loader_FilesystemLoader( + $s->templateDir, + [ + 'extension' => $s->templateExtension, + ], + ); + }, + Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $mfl) => new Mustache_Engine(['loader' => $mfl]), +]); + +return $builder->build(); diff --git a/implementation/11-templating/config/routes.php b/implementation/11-templating/config/routes.php new file mode 100644 index 0000000..1bc00bc --- /dev/null +++ b/implementation/11-templating/config/routes.php @@ -0,0 +1,12 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); + $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/implementation/11-templating/config/settings.php b/implementation/11-templating/config/settings.php new file mode 100644 index 0000000..ad2ea0c --- /dev/null +++ b/implementation/11-templating/config/settings.php @@ -0,0 +1,9 @@ +aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/11-templating/public/index.php b/implementation/11-templating/public/index.php new file mode 100644 index 0000000..d93da3a --- /dev/null +++ b/implementation/11-templating/public/index.php @@ -0,0 +1,5 @@ +getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render('hello', $data); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/11-templating/src/Action/Other.php b/implementation/11-templating/src/Action/Other.php new file mode 100644 index 0000000..895796e --- /dev/null +++ b/implementation/11-templating/src/Action/Other.php @@ -0,0 +1,19 @@ +getBody(); + + $body->write('This works too!'); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/11-templating/src/Bootstrap.php b/implementation/11-templating/src/Bootstrap.php new file mode 100644 index 0000000..2a45e08 --- /dev/null +++ b/implementation/11-templating/src/Bootstrap.php @@ -0,0 +1,110 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$container = require __DIR__ . '/../config/dependencies.php'; +assert($container instanceof ContainerInterface); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$responseFactory = $container->get(ResponseFactory::class); +assert($responseFactory instanceof ResponseFactory); + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + $handler = $routeInfo[1]; + $vars = $routeInfo[2] ?? []; + foreach ($vars as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $vars['request'] = $request; + $invoker = $container->get(InvokerInterface::class); + assert($invoker instanceof InvokerInterface); + $response = $invoker->call($handler, $vars); + assert($response instanceof ResponseInterface); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = $responseFactory->createResponse(405); +} catch (NotFound) { + $response = $responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/11-templating/src/Exception/InternalServerError.php b/implementation/11-templating/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/11-templating/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +engine->render($template, $data); + } +} diff --git a/implementation/11-templating/src/Template/Renderer.php b/implementation/11-templating/src/Template/Renderer.php new file mode 100644 index 0000000..ff916ed --- /dev/null +++ b/implementation/11-templating/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data = []): string; +} diff --git a/implementation/11-templating/templates/hello.html b/implementation/11-templating/templates/hello.html new file mode 100644 index 0000000..0e21f2a --- /dev/null +++ b/implementation/11-templating/templates/hello.html @@ -0,0 +1,11 @@ + + + + + Hello World + + +

Hello {{name}}

+

The time is {{now}}

+ + \ No newline at end of file diff --git a/implementation/12-configuration/.php-cs-fixer.php b/implementation/12-configuration/.php-cs-fixer.php new file mode 100644 index 0000000..705a7d7 --- /dev/null +++ b/implementation/12-configuration/.php-cs-fixer.php @@ -0,0 +1,38 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config' + ]) + ); \ No newline at end of file diff --git a/implementation/12-configuration/.phpcs.xml.dist b/implementation/12-configuration/.phpcs.xml.dist new file mode 100644 index 0000000..3b433f6 --- /dev/null +++ b/implementation/12-configuration/.phpcs.xml.dist @@ -0,0 +1,9 @@ + + + + + src + config + + + \ No newline at end of file diff --git a/implementation/12-configuration/composer.json b/implementation/12-configuration/composer.json new file mode 100644 index 0000000..2c61bdd --- /dev/null +++ b/implementation/12-configuration/composer.json @@ -0,0 +1,46 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3", + "mustache/mustache": "^2.14" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require-dev": { + "phpstan/phpstan": "^1.5", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0", + "squizlabs/php_codesniffer": "^3.6", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.1", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "mnapoli/hard-mode": "^0.3.0" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + } +} diff --git a/implementation/12-configuration/composer.lock b/implementation/12-configuration/composer.lock new file mode 100644 index 0000000..37ee56e --- /dev/null +++ b/implementation/12-configuration/composer.lock @@ -0,0 +1,1550 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "e237b9a5b4210f235b1609a7ce3e065a", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "doctrine/coding-standard", + "version": "8.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/coding-standard.git", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Müller", + "email": "st.mueller@dzh-online.de" + } + ], + "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "doctrine", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/doctrine/coding-standard/issues", + "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" + }, + "time": "2021-04-03T10:54:55+00:00" + }, + { + "name": "mnapoli/hard-mode", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/hard-mode.git", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", + "shasum": "" + }, + "require": { + "doctrine/coding-standard": "^8.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Hard mode for PHP", + "support": { + "issues": "https://github.com/mnapoli/hard-mode/issues", + "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" + }, + "time": "2020-10-12T07:54:37+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.5.0" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-24T18:18:00+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^1.2.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" + }, + "time": "2021-11-18T09:30:29+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/12-configuration/config/dependencies.php b/implementation/12-configuration/config/dependencies.php new file mode 100644 index 0000000..2957edb --- /dev/null +++ b/implementation/12-configuration/config/dependencies.php @@ -0,0 +1,22 @@ + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), +]; diff --git a/implementation/12-configuration/config/routes.php b/implementation/12-configuration/config/routes.php new file mode 100644 index 0000000..1bc00bc --- /dev/null +++ b/implementation/12-configuration/config/routes.php @@ -0,0 +1,12 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); + $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/implementation/12-configuration/config/settings.php b/implementation/12-configuration/config/settings.php new file mode 100644 index 0000000..0dc42b6 --- /dev/null +++ b/implementation/12-configuration/config/settings.php @@ -0,0 +1,10 @@ +aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/12-configuration/public/index.php b/implementation/12-configuration/public/index.php new file mode 100644 index 0000000..d93da3a --- /dev/null +++ b/implementation/12-configuration/public/index.php @@ -0,0 +1,5 @@ +getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render('hello', $data); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/12-configuration/src/Action/Other.php b/implementation/12-configuration/src/Action/Other.php new file mode 100644 index 0000000..895796e --- /dev/null +++ b/implementation/12-configuration/src/Action/Other.php @@ -0,0 +1,19 @@ +getBody(); + + $body->write('This works too!'); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/12-configuration/src/Bootstrap.php b/implementation/12-configuration/src/Bootstrap.php new file mode 100644 index 0000000..f47341e --- /dev/null +++ b/implementation/12-configuration/src/Bootstrap.php @@ -0,0 +1,111 @@ +getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$responseFactory = $container->get(ResponseFactory::class); +assert($responseFactory instanceof ResponseFactory); + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + $handler = $routeInfo[1]; + $vars = $routeInfo[2] ?? []; + foreach ($vars as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $vars['request'] = $request; + $invoker = $container->get(InvokerInterface::class); + assert($invoker instanceof InvokerInterface); + $response = $invoker->call($handler, $vars); + assert($response instanceof ResponseInterface); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = $responseFactory->createResponse(405); +} catch (NotFound) { + $response = $responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/12-configuration/src/Exception/InternalServerError.php b/implementation/12-configuration/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/12-configuration/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +filePath; + } +} diff --git a/implementation/12-configuration/src/Factory/SettingsContainerProvider.php b/implementation/12-configuration/src/Factory/SettingsContainerProvider.php new file mode 100644 index 0000000..20609bf --- /dev/null +++ b/implementation/12-configuration/src/Factory/SettingsContainerProvider.php @@ -0,0 +1,25 @@ +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn () => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} diff --git a/implementation/12-configuration/src/Factory/SettingsProvider.php b/implementation/12-configuration/src/Factory/SettingsProvider.php new file mode 100644 index 0000000..ce1c5f0 --- /dev/null +++ b/implementation/12-configuration/src/Factory/SettingsProvider.php @@ -0,0 +1,10 @@ +engine->render($template, $data); + } +} diff --git a/implementation/12-configuration/src/Template/Renderer.php b/implementation/12-configuration/src/Template/Renderer.php new file mode 100644 index 0000000..ff916ed --- /dev/null +++ b/implementation/12-configuration/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data = []): string; +} diff --git a/implementation/12-configuration/templates/hello.html b/implementation/12-configuration/templates/hello.html new file mode 100644 index 0000000..0e21f2a --- /dev/null +++ b/implementation/12-configuration/templates/hello.html @@ -0,0 +1,11 @@ + + + + + Hello World + + +

Hello {{name}}

+

The time is {{now}}

+ + \ No newline at end of file diff --git a/implementation/13-refactoring/.php-cs-fixer.php b/implementation/13-refactoring/.php-cs-fixer.php new file mode 100644 index 0000000..705a7d7 --- /dev/null +++ b/implementation/13-refactoring/.php-cs-fixer.php @@ -0,0 +1,38 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config' + ]) + ); \ No newline at end of file diff --git a/implementation/13-refactoring/.phpcs.xml.dist b/implementation/13-refactoring/.phpcs.xml.dist new file mode 100644 index 0000000..3b433f6 --- /dev/null +++ b/implementation/13-refactoring/.phpcs.xml.dist @@ -0,0 +1,9 @@ + + + + + src + config + + + \ No newline at end of file diff --git a/implementation/13-refactoring/composer.json b/implementation/13-refactoring/composer.json new file mode 100644 index 0000000..92e7538 --- /dev/null +++ b/implementation/13-refactoring/composer.json @@ -0,0 +1,47 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3", + "mustache/mustache": "^2.14", + "psr/http-server-middleware": "^1.0" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require-dev": { + "phpstan/phpstan": "^1.5", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0", + "squizlabs/php_codesniffer": "^3.6", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.1", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "mnapoli/hard-mode": "^0.3.0" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + } +} diff --git a/implementation/13-refactoring/composer.lock b/implementation/13-refactoring/composer.lock new file mode 100644 index 0000000..c3bb867 --- /dev/null +++ b/implementation/13-refactoring/composer.lock @@ -0,0 +1,1607 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "eb13263f65ee72240c5f25ab82caddd0", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/master" + }, + "time": "2018-10-30T17:12:04+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "doctrine/coding-standard", + "version": "8.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/coding-standard.git", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Müller", + "email": "st.mueller@dzh-online.de" + } + ], + "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "doctrine", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/doctrine/coding-standard/issues", + "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" + }, + "time": "2021-04-03T10:54:55+00:00" + }, + { + "name": "mnapoli/hard-mode", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/hard-mode.git", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", + "shasum": "" + }, + "require": { + "doctrine/coding-standard": "^8.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Hard mode for PHP", + "support": { + "issues": "https://github.com/mnapoli/hard-mode/issues", + "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" + }, + "time": "2020-10-12T07:54:37+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.5.0" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-24T18:18:00+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^1.2.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" + }, + "time": "2021-11-18T09:30:29+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/13-refactoring/config/dependencies.php b/implementation/13-refactoring/config/dependencies.php new file mode 100644 index 0000000..c26cbe3 --- /dev/null +++ b/implementation/13-refactoring/config/dependencies.php @@ -0,0 +1,39 @@ + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, + Emitter::class => fn (BasicEmitter $e) => $e, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), + RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, +]; diff --git a/implementation/13-refactoring/config/routes.php b/implementation/13-refactoring/config/routes.php new file mode 100644 index 0000000..1bc00bc --- /dev/null +++ b/implementation/13-refactoring/config/routes.php @@ -0,0 +1,12 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); + $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/implementation/13-refactoring/config/settings.php b/implementation/13-refactoring/config/settings.php new file mode 100644 index 0000000..0dc42b6 --- /dev/null +++ b/implementation/13-refactoring/config/settings.php @@ -0,0 +1,10 @@ +aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/13-refactoring/public/index.php b/implementation/13-refactoring/public/index.php new file mode 100644 index 0000000..d93da3a --- /dev/null +++ b/implementation/13-refactoring/public/index.php @@ -0,0 +1,5 @@ +getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render('hello', $data); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/13-refactoring/src/Action/Other.php b/implementation/13-refactoring/src/Action/Other.php new file mode 100644 index 0000000..895796e --- /dev/null +++ b/implementation/13-refactoring/src/Action/Other.php @@ -0,0 +1,19 @@ +getBody(); + + $body->write('This works too!'); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/13-refactoring/src/Bootstrap.php b/implementation/13-refactoring/src/Bootstrap.php new file mode 100644 index 0000000..2a3cc85 --- /dev/null +++ b/implementation/13-refactoring/src/Bootstrap.php @@ -0,0 +1,40 @@ +getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); diff --git a/implementation/13-refactoring/src/Exception/InternalServerError.php b/implementation/13-refactoring/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/13-refactoring/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +factory::fromGlobals(); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} diff --git a/implementation/13-refactoring/src/Factory/FileSystemSettingsProvider.php b/implementation/13-refactoring/src/Factory/FileSystemSettingsProvider.php new file mode 100644 index 0000000..3a847ec --- /dev/null +++ b/implementation/13-refactoring/src/Factory/FileSystemSettingsProvider.php @@ -0,0 +1,18 @@ +filePath; + } +} diff --git a/implementation/13-refactoring/src/Factory/RequestFactory.php b/implementation/13-refactoring/src/Factory/RequestFactory.php new file mode 100644 index 0000000..2b17abc --- /dev/null +++ b/implementation/13-refactoring/src/Factory/RequestFactory.php @@ -0,0 +1,11 @@ +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn () => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} diff --git a/implementation/13-refactoring/src/Factory/SettingsProvider.php b/implementation/13-refactoring/src/Factory/SettingsProvider.php new file mode 100644 index 0000000..ce1c5f0 --- /dev/null +++ b/implementation/13-refactoring/src/Factory/SettingsProvider.php @@ -0,0 +1,10 @@ +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + + echo $response->getBody(); + } +} diff --git a/implementation/13-refactoring/src/Http/Emitter.php b/implementation/13-refactoring/src/Http/Emitter.php new file mode 100644 index 0000000..ce4c035 --- /dev/null +++ b/implementation/13-refactoring/src/Http/Emitter.php @@ -0,0 +1,10 @@ +getAttribute($this->routeAttributeName, false); + assert($handler !== false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} diff --git a/implementation/13-refactoring/src/Http/RouteMiddleware.php b/implementation/13-refactoring/src/Http/RouteMiddleware.php new file mode 100644 index 0000000..e3df6f8 --- /dev/null +++ b/implementation/13-refactoring/src/Http/RouteMiddleware.php @@ -0,0 +1,69 @@ +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} diff --git a/implementation/13-refactoring/src/Http/RoutedRequestHandler.php b/implementation/13-refactoring/src/Http/RoutedRequestHandler.php new file mode 100644 index 0000000..a7407c9 --- /dev/null +++ b/implementation/13-refactoring/src/Http/RoutedRequestHandler.php @@ -0,0 +1,10 @@ +routeMiddleware->process($request, $this->handler); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} diff --git a/implementation/13-refactoring/src/Service/Time/Now.php b/implementation/13-refactoring/src/Service/Time/Now.php new file mode 100644 index 0000000..79224b3 --- /dev/null +++ b/implementation/13-refactoring/src/Service/Time/Now.php @@ -0,0 +1,10 @@ +engine->render($template, $data); + } +} diff --git a/implementation/13-refactoring/src/Template/Renderer.php b/implementation/13-refactoring/src/Template/Renderer.php new file mode 100644 index 0000000..ff916ed --- /dev/null +++ b/implementation/13-refactoring/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data = []): string; +} diff --git a/implementation/13-refactoring/templates/hello.html b/implementation/13-refactoring/templates/hello.html new file mode 100644 index 0000000..0e21f2a --- /dev/null +++ b/implementation/13-refactoring/templates/hello.html @@ -0,0 +1,11 @@ + + + + + Hello World + + +

Hello {{name}}

+

The time is {{now}}

+ + \ No newline at end of file From b12cf019e7b1804f961e257d5ce97b8c2599d7c4 Mon Sep 17 00:00:00 2001 From: lubiana Date: Thu, 31 Mar 2022 08:34:30 +0200 Subject: [PATCH 119/314] asdf --- 14-middleware.md | 142 +- app/.php-cs-fixer.php | 38 + app/.phpcs.xml.dist | 9 + app/composer.json | 52 + app/composer.lock | 2308 +++++++++++++++++ app/config/dependencies.php | 51 + app/config/middlewares.php | 12 + app/config/routes.php | 14 + app/config/settings.php | 12 + app/data/pages/01-front-controller.md | 53 + app/data/pages/02-composer.md | 75 + app/data/pages/03-error-handler.md | 79 + app/data/pages/04-development-helpers.md | 260 ++ app/data/pages/05-http.md | 124 + app/data/pages/06-router.md | 101 + app/data/pages/07-dispatching-to-a-class.md | 137 + app/data/pages/08-inversion-of-control.md | 54 + app/data/pages/09-dependency-injector.md | 213 ++ app/data/pages/10-invoker.md | 102 + app/data/pages/11-templating.md | 240 ++ app/data/pages/12-configuration.md | 201 ++ app/data/pages/13-refactoring.md | 377 +++ app/data/pages/14-middleware.md | 298 +++ app/phpstan-baseline.neon | 7 + app/phpstan.neon | 8 + app/public/css/spectre-exp.min.css | 1 + app/public/css/spectre-icons.min.css | 1 + app/public/css/spectre.min.css | 1 + app/public/index.php | 5 + app/src/Action/Hello.php | 31 + app/src/Action/Other.php | 19 + app/src/Action/Page.php | 35 + app/src/Bootstrap.php | 40 + app/src/Exception/InternalServerError.php | 9 + app/src/Exception/MethodNotAllowed.php | 9 + app/src/Exception/NotFound.php | 9 + app/src/Factory/ContainerProvider.php | 10 + app/src/Factory/DiactorosRequestFactory.php | 28 + .../Factory/FileSystemSettingsProvider.php | 22 + app/src/Factory/PipelineProvider.php | 25 + app/src/Factory/RequestFactory.php | 11 + app/src/Factory/SettingsContainerProvider.php | 25 + app/src/Factory/SettingsProvider.php | 10 + app/src/Http/AddRoute.php | 17 - app/src/Http/BasicEmitter.php | 38 + app/src/Http/ContainerPipeline.php | 82 + app/src/Http/Emitter.php | 10 + app/src/Http/InvokerRoutedHandler.php | 34 + app/src/Http/Pipeline.php | 11 + app/src/Http/RouteDecorationMiddleware.php | 97 - app/src/Http/RouteMiddleware.php | 69 + app/src/Http/RoutedRequestHandler.php | 10 + app/src/Kernel.php | 49 +- app/src/Middleware/CacheMiddleware.php | 33 + app/src/Model/MarkdownPage.php | 13 + app/src/Repository/CachedMarkdownPageRepo.php | 46 + app/src/Repository/MarkdownPageFilesystem.php | 63 + app/src/Repository/MarkdownPageRepo.php | 17 + app/src/Service/Time/Now.php | 10 + app/src/Service/Time/SystemClockNow.php | 13 + app/src/Settings.php | 16 + app/src/Template/MustacheRenderer.php | 17 + app/src/Template/Renderer.php | 11 + app/templates/hello.html | 6 + app/templates/page.html | 5 + app/templates/partials/foot.html | 3 + app/templates/partials/head.html | 11 + .../14-middleware/.php-cs-fixer.php | 38 + implementation/14-middleware/.phpcs.xml.dist | 9 + implementation/14-middleware/composer.json | 50 + implementation/14-middleware/composer.lock | 1815 +++++++++++++ .../14-middleware/config/dependencies.php | 42 + .../14-middleware/config/middlewares.php | 11 + .../14-middleware/config/routes.php | 12 + .../14-middleware/config/settings.php | 11 + .../14-middleware/phpstan-baseline.neon | 7 + implementation/14-middleware/phpstan.neon | 8 + .../14-middleware/public/favicon.ico | Bin 0 -> 15086 bytes implementation/14-middleware/public/index.php | 5 + implementation/14-middleware/src/.gitkeep | 0 .../14-middleware/src/Action/Hello.php | 31 + .../14-middleware/src/Action/Other.php | 19 + .../14-middleware/src/Bootstrap.php | 40 + .../src/Exception/InternalServerError.php | 9 + .../src/Exception/MethodNotAllowed.php | 9 + .../14-middleware/src/Exception/NotFound.php | 9 + .../src/Factory/ContainerProvider.php | 10 + .../src/Factory/DiactorosRequestFactory.php | 28 + .../Factory/FileSystemSettingsProvider.php | 22 + .../src/Factory/PipelineProvider.php | 25 + .../src/Factory/RequestFactory.php | 11 + .../src/Factory/SettingsContainerProvider.php | 25 + .../src/Factory/SettingsProvider.php | 10 + .../14-middleware/src/Http/BasicEmitter.php | 38 + .../src/Http/ContainerPipeline.php | 82 + .../14-middleware/src/Http/Emitter.php | 10 + .../src/Http/InvokerRoutedHandler.php | 34 + .../14-middleware/src/Http/Pipeline.php | 11 + .../src/Http/RouteMiddleware.php | 69 + .../src/Http/RoutedRequestHandler.php | 10 + implementation/14-middleware/src/Kernel.php | 32 + .../14-middleware/src/Service/Time/Now.php | 10 + .../src/Service/Time/SystemClockNow.php | 13 + implementation/14-middleware/src/Settings.php | 15 + .../src/Template/MustacheRenderer.php | 17 + .../14-middleware/src/Template/Renderer.php | 11 + .../14-middleware/templates/hello.html | 11 + 107 files changed, 8372 insertions(+), 186 deletions(-) create mode 100644 app/.php-cs-fixer.php create mode 100644 app/.phpcs.xml.dist create mode 100644 app/composer.json create mode 100644 app/composer.lock create mode 100644 app/config/dependencies.php create mode 100644 app/config/middlewares.php create mode 100644 app/config/routes.php create mode 100644 app/config/settings.php create mode 100644 app/data/pages/01-front-controller.md create mode 100644 app/data/pages/02-composer.md create mode 100644 app/data/pages/03-error-handler.md create mode 100644 app/data/pages/04-development-helpers.md create mode 100644 app/data/pages/05-http.md create mode 100644 app/data/pages/06-router.md create mode 100644 app/data/pages/07-dispatching-to-a-class.md create mode 100644 app/data/pages/08-inversion-of-control.md create mode 100644 app/data/pages/09-dependency-injector.md create mode 100644 app/data/pages/10-invoker.md create mode 100644 app/data/pages/11-templating.md create mode 100644 app/data/pages/12-configuration.md create mode 100644 app/data/pages/13-refactoring.md create mode 100644 app/data/pages/14-middleware.md create mode 100644 app/phpstan-baseline.neon create mode 100644 app/phpstan.neon create mode 100644 app/public/css/spectre-exp.min.css create mode 100644 app/public/css/spectre-icons.min.css create mode 100644 app/public/css/spectre.min.css create mode 100644 app/public/index.php create mode 100644 app/src/Action/Hello.php create mode 100644 app/src/Action/Other.php create mode 100644 app/src/Action/Page.php create mode 100644 app/src/Bootstrap.php create mode 100644 app/src/Exception/InternalServerError.php create mode 100644 app/src/Exception/MethodNotAllowed.php create mode 100644 app/src/Exception/NotFound.php create mode 100644 app/src/Factory/ContainerProvider.php create mode 100644 app/src/Factory/DiactorosRequestFactory.php create mode 100644 app/src/Factory/FileSystemSettingsProvider.php create mode 100644 app/src/Factory/PipelineProvider.php create mode 100644 app/src/Factory/RequestFactory.php create mode 100644 app/src/Factory/SettingsContainerProvider.php create mode 100644 app/src/Factory/SettingsProvider.php delete mode 100644 app/src/Http/AddRoute.php create mode 100644 app/src/Http/BasicEmitter.php create mode 100644 app/src/Http/ContainerPipeline.php create mode 100644 app/src/Http/Emitter.php create mode 100644 app/src/Http/InvokerRoutedHandler.php create mode 100644 app/src/Http/Pipeline.php delete mode 100644 app/src/Http/RouteDecorationMiddleware.php create mode 100644 app/src/Http/RouteMiddleware.php create mode 100644 app/src/Http/RoutedRequestHandler.php create mode 100644 app/src/Middleware/CacheMiddleware.php create mode 100644 app/src/Model/MarkdownPage.php create mode 100644 app/src/Repository/CachedMarkdownPageRepo.php create mode 100644 app/src/Repository/MarkdownPageFilesystem.php create mode 100644 app/src/Repository/MarkdownPageRepo.php create mode 100644 app/src/Service/Time/Now.php create mode 100644 app/src/Service/Time/SystemClockNow.php create mode 100644 app/src/Settings.php create mode 100644 app/src/Template/MustacheRenderer.php create mode 100644 app/src/Template/Renderer.php create mode 100644 app/templates/hello.html create mode 100644 app/templates/page.html create mode 100644 app/templates/partials/foot.html create mode 100644 app/templates/partials/head.html create mode 100644 implementation/14-middleware/.php-cs-fixer.php create mode 100644 implementation/14-middleware/.phpcs.xml.dist create mode 100644 implementation/14-middleware/composer.json create mode 100644 implementation/14-middleware/composer.lock create mode 100644 implementation/14-middleware/config/dependencies.php create mode 100644 implementation/14-middleware/config/middlewares.php create mode 100644 implementation/14-middleware/config/routes.php create mode 100644 implementation/14-middleware/config/settings.php create mode 100644 implementation/14-middleware/phpstan-baseline.neon create mode 100644 implementation/14-middleware/phpstan.neon create mode 100644 implementation/14-middleware/public/favicon.ico create mode 100644 implementation/14-middleware/public/index.php create mode 100644 implementation/14-middleware/src/.gitkeep create mode 100644 implementation/14-middleware/src/Action/Hello.php create mode 100644 implementation/14-middleware/src/Action/Other.php create mode 100644 implementation/14-middleware/src/Bootstrap.php create mode 100644 implementation/14-middleware/src/Exception/InternalServerError.php create mode 100644 implementation/14-middleware/src/Exception/MethodNotAllowed.php create mode 100644 implementation/14-middleware/src/Exception/NotFound.php create mode 100644 implementation/14-middleware/src/Factory/ContainerProvider.php create mode 100644 implementation/14-middleware/src/Factory/DiactorosRequestFactory.php create mode 100644 implementation/14-middleware/src/Factory/FileSystemSettingsProvider.php create mode 100644 implementation/14-middleware/src/Factory/PipelineProvider.php create mode 100644 implementation/14-middleware/src/Factory/RequestFactory.php create mode 100644 implementation/14-middleware/src/Factory/SettingsContainerProvider.php create mode 100644 implementation/14-middleware/src/Factory/SettingsProvider.php create mode 100644 implementation/14-middleware/src/Http/BasicEmitter.php create mode 100644 implementation/14-middleware/src/Http/ContainerPipeline.php create mode 100644 implementation/14-middleware/src/Http/Emitter.php create mode 100644 implementation/14-middleware/src/Http/InvokerRoutedHandler.php create mode 100644 implementation/14-middleware/src/Http/Pipeline.php create mode 100644 implementation/14-middleware/src/Http/RouteMiddleware.php create mode 100644 implementation/14-middleware/src/Http/RoutedRequestHandler.php create mode 100644 implementation/14-middleware/src/Kernel.php create mode 100644 implementation/14-middleware/src/Service/Time/Now.php create mode 100644 implementation/14-middleware/src/Service/Time/SystemClockNow.php create mode 100644 implementation/14-middleware/src/Settings.php create mode 100644 implementation/14-middleware/src/Template/MustacheRenderer.php create mode 100644 implementation/14-middleware/src/Template/Renderer.php create mode 100644 implementation/14-middleware/templates/hello.html diff --git a/14-middleware.md b/14-middleware.md index f4d3f3b..e698327 100644 --- a/14-middleware.md +++ b/14-middleware.md @@ -63,7 +63,7 @@ final class CachingMiddleware implements MiddlewareInterface public function __construct(private CacheInterface $cache){} public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { - if ($request->getAttribute('isAuthenticated', false)) { + if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { $key = $request->getUri()->getPath(); return $this->cache->get($key, fn() => $handler->handle($request), 10); } @@ -135,16 +135,33 @@ interface Pipeline And our implementation looks something like this: ```php -final class SimplePipeline implements Pipeline + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ public function __construct( - private readonly array $middlewares, - private RequestHandlerInterface $tip - ){} + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } public function dispatch(ServerRequestInterface $request): ResponseInterface { @@ -156,19 +173,49 @@ final class SimplePipeline implements Pipeline { foreach (array_reverse($this->middlewares) as $middleware) { $next = $this->tip; - $this->tip = new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $next - ){} - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->next); - } - }; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } } } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } } ``` @@ -180,27 +227,58 @@ and store that itself as the current tip. There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) -Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline: +Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline +Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline + ```php - Pipeline::class => function ( - RouteDecoratedRequestHandler $tip, - RouteDecorationMiddleware $router, - ) { - $middlewares = require __DIR__ . '/middlewares.php'; - $middlewares[] = $router; - return new SimplePipeline($middlewares, $tip); - }, +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} +``` + +And configure the container to use the Factory to create the Pipeline: + +```php + ..., + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + ... ``` And of course a new file called middlewares.php in our config folder: ```php -setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config' + ]) + ); \ No newline at end of file diff --git a/app/.phpcs.xml.dist b/app/.phpcs.xml.dist new file mode 100644 index 0000000..3b433f6 --- /dev/null +++ b/app/.phpcs.xml.dist @@ -0,0 +1,9 @@ + + + + + src + config + + + \ No newline at end of file diff --git a/app/composer.json b/app/composer.json new file mode 100644 index 0000000..9cbc356 --- /dev/null +++ b/app/composer.json @@ -0,0 +1,52 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3", + "mustache/mustache": "^2.14", + "psr/http-server-middleware": "^1.0", + "middlewares/trailing-slash": "^2.0", + "middlewares/whoops": "^2.0", + "erusev/parsedown": "^1.7", + "symfony/cache": "^6.0" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require-dev": { + "phpstan/phpstan": "^1.5", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0", + "squizlabs/php_codesniffer": "^3.6", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.1", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "mnapoli/hard-mode": "^0.3.0", + "psalm/phar": "^4.22" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + } +} diff --git a/app/composer.lock b/app/composer.lock new file mode 100644 index 0000000..b10158c --- /dev/null +++ b/app/composer.lock @@ -0,0 +1,2308 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "0f81a412e25ca1269493dface2804540", + "packages": [ + { + "name": "erusev/parsedown", + "version": "1.7.4", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "support": { + "issues": "https://github.com/erusev/parsedown/issues", + "source": "https://github.com/erusev/parsedown/tree/1.7.x" + }, + "time": "2019-12-30T22:54:17+00:00" + }, + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "middlewares/trailing-slash", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/middlewares/trailing-slash.git", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", + "shasum": "" + }, + "require": { + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to normalize the trailing slash of the uri path", + "homepage": "https://github.com/middlewares/trailing-slash", + "keywords": [ + "http", + "middleware", + "normalize", + "path", + "psr-15", + "psr-7", + "slash" + ], + "support": { + "issues": "https://github.com/middlewares/trailing-slash/issues", + "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" + }, + "time": "2020-12-02T00:06:55+00:00" + }, + { + "name": "middlewares/utils", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/middlewares/utils.git", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v2.16", + "guzzlehttp/psr7": "^2.0", + "laminas/laminas-diactoros": "^2.4", + "nyholm/psr7": "^1.0", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "slim/psr7": "^1.4", + "squizlabs/php_codesniffer": "^3.5", + "sunrise/http-message": "^1.0", + "sunrise/http-server-request": "^1.0", + "sunrise/stream": "^1.0.15", + "sunrise/uri": "^1.0.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\Utils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Common utils for PSR-15 middleware packages", + "homepage": "https://github.com/middlewares/utils", + "keywords": [ + "PSR-11", + "http", + "middleware", + "psr-15", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/middlewares/utils/issues", + "source": "https://github.com/middlewares/utils/tree/v3.3.0" + }, + "time": "2021-07-04T17:56:23+00:00" + }, + { + "name": "middlewares/whoops", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/middlewares/whoops.git", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.5", + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^5.0 || ^7.0", + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to use Whoops as error handler", + "homepage": "https://github.com/middlewares/whoops", + "keywords": [ + "error", + "http", + "middleware", + "psr-15", + "psr-7", + "server", + "whoops" + ], + "support": { + "issues": "https://github.com/middlewares/whoops/issues", + "source": "https://github.com/middlewares/whoops/tree/v2.0.2" + }, + "time": "2022-01-27T20:31:30+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/master" + }, + "time": "2018-10-30T17:12:04+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "symfony/cache", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", + "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^1.1.7|^2|^3", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/var-exporter": "^5.4|^6.0" + }, + "conflict": { + "doctrine/dbal": "<2.13.1", + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/var-dumper": "<5.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^2.13.1|^3.0", + "predis/predis": "^1.1", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/2f7463f156cf9c665d9317e21a809c3bbff5754e", + "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "psr/cache": "^3.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.0.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-08-17T15:35:52+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-07-12T14:48:14+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-04T16:48:04+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "130229a482abf17635a685590958894dfb4b4360" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/130229a482abf17635a685590958894dfb4b4360", + "reference": "130229a482abf17635a685590958894dfb4b4360", + "shasum": "" + }, + "require": { + "php": ">=8.0.2" + }, + "require-dev": { + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "doctrine/coding-standard", + "version": "8.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/coding-standard.git", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Müller", + "email": "st.mueller@dzh-online.de" + } + ], + "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "doctrine", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/doctrine/coding-standard/issues", + "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" + }, + "time": "2021-04-03T10:54:55+00:00" + }, + { + "name": "mnapoli/hard-mode", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/hard-mode.git", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", + "shasum": "" + }, + "require": { + "doctrine/coding-standard": "^8.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Hard mode for PHP", + "support": { + "issues": "https://github.com/mnapoli/hard-mode/issues", + "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" + }, + "time": "2020-10-12T07:54:37+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.5.0" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-24T18:18:00+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^1.2.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" + }, + "time": "2021-11-18T09:30:29+00:00" + }, + { + "name": "psalm/phar", + "version": "4.22.0", + "source": { + "type": "git", + "url": "https://github.com/psalm/phar.git", + "reference": "feebed09c9782d9aaa819b794d880c2671ba0e4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/psalm/phar/zipball/feebed09c9782d9aaa819b794d880c2671ba0e4c", + "reference": "feebed09c9782d9aaa819b794d880c2671ba0e4c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "vimeo/psalm": "*" + }, + "bin": [ + "psalm.phar" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer-based Psalm Phar", + "support": { + "issues": "https://github.com/psalm/phar/issues", + "source": "https://github.com/psalm/phar/tree/4.22.0" + }, + "time": "2022-02-27T11:01:37+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/app/config/dependencies.php b/app/config/dependencies.php new file mode 100644 index 0000000..e1ea3bf --- /dev/null +++ b/app/config/dependencies.php @@ -0,0 +1,51 @@ + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, + Emitter::class => fn (BasicEmitter $e) => $e, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + CacheInterface::class => fn (FilesystemAdapter $a) => $a, + MarkdownPageFilesystem::class => fn (Settings $s) => new MarkdownPageFilesystem($s->pagesPath), + CachedMarkdownPageRepo::class => fn (CacheInterface $c, MarkdownPageFilesystem $r) => new CachedMarkdownPageRepo($c, $r), + MarkdownPageRepo::class => fn (MarkdownPageFilesystem $r) => $r, +]; diff --git a/app/config/middlewares.php b/app/config/middlewares.php new file mode 100644 index 0000000..891cc83 --- /dev/null +++ b/app/config/middlewares.php @@ -0,0 +1,12 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/page/{page}', Page::class); + $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); + $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/app/config/settings.php b/app/config/settings.php new file mode 100644 index 0000000..8a0861d --- /dev/null +++ b/app/config/settings.php @@ -0,0 +1,12 @@ +>](02-composer.md) + +### Front Controller + +A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. + +To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. + +A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. + +The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. + +But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. + +So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. + +Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: + +```php +>](02-composer.md) diff --git a/app/data/pages/02-composer.md b/app/data/pages/02-composer.md new file mode 100644 index 0000000..a25a4a8 --- /dev/null +++ b/app/data/pages/02-composer.md @@ -0,0 +1,75 @@ +[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) + +### Composer + +[Composer](https://getcomposer.org/) is a dependency manager for PHP. + +Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do +something. With Composer, you can install third-party libraries for your application. + +If you don't have Composer installed already, head over to the website and install it. You can find Composer packages +for your project on [Packagist](https://packagist.org/). + +Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will +be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. + +Add the following content to the file: + +```json +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubiana", + "email": "lubiana@hannover.ccc.de" + } + ] +} +``` + +In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use +whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. +Just replace it with your namespace in your own code. + +I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. + +As the Bootstrap.php file is placed in that directory we should +add the namespace to the File as well. Here is my current Bootstrap.php +as a reference: + +```php +>](03-error-handler.md) diff --git a/app/data/pages/03-error-handler.md b/app/data/pages/03-error-handler.md new file mode 100644 index 0000000..60465d0 --- /dev/null +++ b/app/data/pages/03-error-handler.md @@ -0,0 +1,79 @@ +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) + +### Error Handler + +An error handler allows you to customize what happens if your code results in an error. + +A nice error page with a lot of information for debugging goes a long way during development. So the first package +for your application will take care of that. + +I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. +If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, +you have total control over your project. + +An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) + +To install a new package, open up your `composer.json` and add the package to the require part. It should now look +like this: + +```php +"require": { + "php": ">=8.1.0", + "filp/whoops": "^2.14" +}, +``` + +Now run `composer update` in your console and it will be installed. + +Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, +i that case composer automatically installs the package and updates your composer.json-file. + +But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, +ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you +only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. + +**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message +can help someone to gain access to your system. Always show a user friendly error page instead and send an email to +yourself, write to a log or something similar. So only you can see the errors in the production environment. + +For development that does not make sense though -- you want a nice error page. The solution is to have an environment +switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in +case no environment has been set. + +Then after the error handler registration, throw an `Exception` to test if everything is working correctly. +Your `Bootstrap.php` should now look similar to this: + +```php +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (\Throwable $e) { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +throw new \Exception("Ooooopsie"); + +``` + +You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until +you get it working. Now would also be a good time for another commit. + + +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/app/data/pages/04-development-helpers.md b/app/data/pages/04-development-helpers.md new file mode 100644 index 0000000..74f913c --- /dev/null +++ b/app/data/pages/04-development-helpers.md @@ -0,0 +1,260 @@ +[<< previous](03-error-handler.md) | [next >>](05-http.md) + +### Development Helpers + +I have added some more helpers to my composer.json that help me with development. As these are scripts and programms +used only for development they should not be used in a production environment. Composer has a specific sections in its +file called "dev-dependencies", everything that is required in this section does not get installen in production. + +Let's install our dev-helpers and i will explain them one by one: +`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` + +#### Static Code Analysis with phpstan + +Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or +create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements +that are not (or not always) available, if-statements that always are true and a lot of other stuff. + +A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. + +```php +/** + * @param \DateTime $date + * @return void + */ +function printDate($date) { + $date->format('Y-m-d H:i:s'); +} + +printDate('now'); +``` +if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` + +It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has +no use, and secondly, that we are calling the function with a string instead of a datetime object. + +```shell +1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + ------ --------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ --------------------------------------------------------------------------------------------- +30 Call to method DateTime::format() on a separate line has no effect. +33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. + ------ --------------------------------------------------------------------------------------------- +``` + +The second error is something that "declare strict-types" already catches for us, but the first error is something that +we usually would not discover easily without speccially looking for this errortype. + +We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and +path everytime we want to check our code for errors: + +```yaml +parameters: + level: max + paths: + - src +``` +now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project + +With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us +some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. + +```shell +composer require --dev phpstan/extension-installer +composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules +``` + +During the first install you need to allow the extension installer to actually install the extension. The second command +installs some more strict rulesets and activates them in phpstan. + +If we now rerun phpstan it already tells us about some errors we have made: + +``` + ------ ----------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ ----------------------------------------------------------------------------------------------- +10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider + using long ternary. +25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: + http://bit.ly/subtypeexception +26 Unreachable statement - code above always terminates. + ------ ----------------------------------------------------------------------------------------------- +``` + +The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove +that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific +problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working +on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages +everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we +are adding to our code. + +In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the +phpstan.neon file and run phpstan with the +'--generate-baseline' option: + +```yaml +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` +```shell +[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline +Note: Using configuration file /home/vagrant/app/phpstan.neon. + 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + + + [OK] Baseline generated with 1 error. + + +``` + +you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) + +#### PHP-CS-Fixer + +Another great tool is the php-cs-fixer, which just applies a specific style to your code. + +when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current +directory. + +You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) + +personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup +a configuration file that i use in all my projects .php-cs-fixer.php: + +```php +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + ]) + ); +``` + +#### PHP Codesniffer + +The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra +rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. + +it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. + +Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have +guessed we are adding some extra rules. + +Lets install the ruleset with composer +```shell +composer require --dev mnapoli/hard-mode +``` + +and add a configuration file to actually use it '.phpcs.xml.dist' +```xml + + + + + src + + + +``` + +running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. + +``` +[vagrant@archlinux app]$ ./vendor/bin/phpcs + +FILE: src/Bootstrap.php +---------------------------------------------------------------------------------------------------- +FOUND 4 ERRORS AFFECTING 4 LINES +---------------------------------------------------------------------------------------------------- + 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. + 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead +---------------------------------------------------------------------------------------------------- +PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY +---------------------------------------------------------------------------------------------------- + +Time: 639ms; Memory: 10MB +``` + +You can then use `./vendor/bin/phpcbf` to try to fix them + + +#### Symfony Var-Dumper + +another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small +functions. + +dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects +or arrays. + +dd() on the other hand is a function that dumps its parameters and then exits the php-script. + +you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. + +#### Composer scripts + +now we have a few commands that are available on the command line. i personally do not like to type complex commands +with lots of parameters by hand all the time, so i added a few lines to my composer.json: + +```json +"scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" +}, +``` + +that way i can just type "composer" followed by the command name in the root of my project. if i want to start the +php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the +time. + +You could also configure PhpStorm to automatically run these commands in the background and highlight the violations +directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my +flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. + +My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before +commiting and pushing the code. + +[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/app/data/pages/05-http.md b/app/data/pages/05-http.md new file mode 100644 index 0000000..6166214 --- /dev/null +++ b/app/data/pages/05-http.md @@ -0,0 +1,124 @@ +[<< previous](04-development-helpers.md) | [next >>](06-router.md) + +### HTTP + +PHP already has a few things built in to make working with HTTP easier. For example there are the +[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. + +These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, +if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, +then you will want a class with a nice object-oriented interface that you can use in your application instead. + +Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The +standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php +projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. + +As this is a widely adopted standard there are already several implementations available for us to use. I will choose +the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. + +Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a +[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. + +Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use +that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding +the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). + + +to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit +enter + +Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): + +```php +$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); +$response = new \Laminas\Diactoros\Response; +$response->getBody()->write('Hello World! '); +$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); +``` + +This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. + +In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the +write()-Method on that object. + + +To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: + +```php +echo $response->getBody(); +``` + +This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only +stores data. + +You can play around with the other methods of the Request object and take a look at its content with the dd() function. + +```php +dd($response) +``` + +Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot +be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which +creates a copy of the request object with the changed property and returns that clone: + +```php +$response = $response->withStatus(200); +$response = $response->withAddedHeader('Content-type', 'application/json'); +``` + +If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been +defined this way. + +But if you have been keeping attention you might argue that the following line should not work if the request object is +immutable. + +```php +$response->getBody()->write('Hello World!'); +``` + +The response-body implements a stream interface which is immutable for some reasons that are described in the +[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be +aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in +the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. +I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content +to a response object. + +```php +$body = $response->getBody(); +$body->write('Hello World!'); +$response = $response->withBody($body); +``` + +Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our +output-logic a little bit more. Replace the line that echos the response-body with the following: + +```php +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); +``` + +This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a +webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. + +Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. + +Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter + + +[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/app/data/pages/06-router.md b/app/data/pages/06-router.md new file mode 100644 index 0000000..6c39ae5 --- /dev/null +++ b/app/data/pages/06-router.md @@ -0,0 +1,101 @@ +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) + +### Router + +A router dispatches to different handlers depending on rules that you have set up. + +With your current setup it does not matter what URL is used to access the application, it will always result in the same +response. So let's fix that now. + +I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own +favorite package. + +Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) + +By now you know how to install Composer packages, so I will leave that to you. + +Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. + +```php +$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +switch ($routeInfo[0]) { + case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: + $response = (new \Laminas\Diactoros\Response)->withStatus(405); + $response->getBody()->write('Method not allowed'); + $response = $response->withStatus(405); + break; + case \FastRoute\Dispatcher::FOUND: + $handler = $routeInfo[1]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + /** @var \Psr\Http\Message\ResponseInterface $response */ + $response = call_user_func($handler, $request); + break; + case \FastRoute\Dispatcher::NOT_FOUND: + default: + $response = (new \Laminas\Diactoros\Response)->withStatus(404); + $response->getBody()->write('Not Found!'); + break; +} +``` + +In the first part of the code, you are registering the available routes for your application. In the second part, the +dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, +we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. +If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. + +This setup might work for really small applications, but once you start adding a few routes your bootstrap file will +quickly get cluttered. So let's move them out into a separate file. + +Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; + +```php +addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}; +``` + +Now let's rewrite the route dispatcher part to use the `Routes.php` file. + +```php +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); +``` + +This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. + +Of course we now need to add the 'config' folder to the configuration files of our +devhelpers so that they can scan that directory as well. + +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/app/data/pages/07-dispatching-to-a-class.md b/app/data/pages/07-dispatching-to-a-class.md new file mode 100644 index 0000000..0c961a4 --- /dev/null +++ b/app/data/pages/07-dispatching-to-a-class.md @@ -0,0 +1,137 @@ +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) + +### Dispatching to a Class + +In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). +MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to +learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) +and the followup posts. + +So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). + +We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other +common names are 'Controllers' or 'Actions'. + +Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. +In there, create a `Hello.php` file. + +```php +getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + } +} +``` + +You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) +that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is +fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement +it in some other parts of our application as well. In order to use that Interface we have to require it with composer: +'composer require psr/http-server-handler'. + +The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. +At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. + +Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: + +```php +return function(\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); + $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); +}; +``` + +Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared +the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing +the response to the one we defined for the second route. + +To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: + +```php +case \FastRoute\Dispatcher::FOUND: + $handler = new $routeInfo[1]; + if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { + throw new \Exception('Invalid Requesthandler'); + } + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + assert($response instanceof \Psr\Http\Message\ResponseInterface) + break; +``` + +So instead of just calling a method you are now instantiating an object and then calling the method on it. + +Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. + +And of course don't forget to commit your changes. + +Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still +generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for +example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still +have our whoopsie error-handler but i like to be more explicit in my control flow. + +In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new +Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and +InternalServerError. All three should extend phps Base Exception class. + +Here is my NotFound.php for example. + +```php + $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody()->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} +``` + +Check if our code still works, try to trigger some errors, run phpstan and the fix command +and don't forget to commit your changes. + +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/app/data/pages/08-inversion-of-control.md b/app/data/pages/08-inversion-of-control.md new file mode 100644 index 0000000..21f4f23 --- /dev/null +++ b/app/data/pages/08-inversion-of-control.md @@ -0,0 +1,54 @@ +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) + +### Inversion of Control + +In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we +want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then +we would need to edit every one of our request handlers to call a different constructor of the class. + +The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that +instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done +with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). + +If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is +implemented, it will make sense. + +Change your `Hello` action to the following: + +```php +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: + +```php +$handler = new $className($response); +``` + +Of course we need to also update all the other handlers. + +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/app/data/pages/09-dependency-injector.md b/app/data/pages/09-dependency-injector.md new file mode 100644 index 0000000..7f7c6a2 --- /dev/null +++ b/app/data/pages/09-dependency-injector.md @@ -0,0 +1,213 @@ +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) + +### Dependency Injector + +A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when +the class is instantiated. + +Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work +with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look +for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). + +I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) +out of the box. + +After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: + +```php +addDefinitions([ + \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), +]); + +return $builder->build(); +``` + +In this file we create a containerbuilder, add some definitions to it and return the container. +As the container supports autowiring we only need to define services where we want to use a specific implementation of +an interface. + +In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to +tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second +exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. + +Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), +as we will use that extensively. + +Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally +to create our Handler-Object + +```php +$container = require __DIR__ . '/../config/dependencies.php'; +assert($container instanceof \Psr\Container\ContainerInterface); + +$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); +assert($request instanceof \Psr\Http\Message\ServerRequestInterface); +``` + +The other part that has to be changed is the dispatching of the route. Before you had the following code: + +```php +$className = $routeInfo[1]; +$handler = new $className($response); +assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Change that to the following: + +```php +/** @var RequestHandlerInterface $handler */ +$className = $routeInfo[1]; +$handler = $container->get($className); +assert($handler instanceof RequestHandlerInterface); +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Make sure to use the container fetch the response object in the catch blocks as well: + +```php +} catch (MethodNotAllowed) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(404); + $response->getBody()->write('Not Found'); +} +``` + +Now all your controller constructor dependencies will be automatically resolved with PHP-DI. + +We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons +in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call +`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we +want to work with a different date in a test. + +Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation +that creates us a DateTimeImmutable object with the current date and time. + +src/Service/Time/Now.php: +```php +namespace Lubian\NoFramework\Service\Time; + +interface Now +{ + public function __invoke(): \DateTimeImmutable; +} +``` +src/Service/Time/SystemClockNow.php: +```php +namespace Lubian\NoFramework\Service\Time; + +final class SystemClockNow implements Now +{ + + public function __invoke(): \DateTimeImmutable + { + return new \DateTimeImmutable; + } +} +``` +If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and +update the handle-method to use the new class property: + +```php +getAttribute('name', 'Stranger'); + $nowAsString = ($this->now)()->format('H:i:s'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowAsString); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI +automatically figures out what classes are requested in the constructor and tries to create the objects needed. + +But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred +S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: + +```php + public function __construct( + private ResponseInterface $response, + private Now $now, + ) +``` + +When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation +should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: + +```php +\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), +``` + +we could also use the PHP-DI create method to delegate the object creation to the container implementation: +```php +\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), +``` + +this way the container can try to resolve any dependencies that the class might have internally, but prefer the other +method because we are not depending on this specific dependency injection implementation. + +Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are +requesting the Hello action. + +If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As +we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way +we wont get any warnings for this particular issue, but for any other issues we add to our code. + +Update the phpstan.neon file to include a "baseline" file: + +``` +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` + +if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and +ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just +'baseline' + +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/app/data/pages/10-invoker.md b/app/data/pages/10-invoker.md new file mode 100644 index 0000000..3033fae --- /dev/null +++ b/app/data/pages/10-invoker.md @@ -0,0 +1,102 @@ +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) + +### Invoker + +Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the +one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long +with some services and not the whole Requestobject with all its various properties. + +If we take our Hello action for example we only need a response object, the time service and the 'name' information from +the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named +the class hello and it would be redundant to also call the the method hello. So an updated version of that class could +look like this: + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + string $name = 'Stranger', + ): ResponseInterface + { + $body = $this->response->getBody(); + $nowString = $now->get()->format('H:i:s'); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowString); + return $response + ->withBody($body) + ->withStatus(200); + } +} +``` + +It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short +closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the +rootpath of our application yet. + +```php +$r->addRoute('GET', '/hello[/{name}]', Hello::class); +$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); +$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +``` + +In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the +route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that +class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what +arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router +if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple +callable we would need to manually use reflection as well to resolve all the arguments. + +But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) +which handles all of that for us. + +After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo +switch section in the Bootstrap.php file to use the container->call() method: + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2]; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$response = $container->call($handler, $args); +``` + +Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. + +But by now you should know that I do not like to depend on specific implementations and the call method is not defined in +the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container +or any other implementation. + +Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific +container implementation so we could use it with any container that implements the ContainerInterface. And best of all +the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that +we could implement if we ever want to write our own implementation or we could write an adapter that uses a different +class that solves the same problem. + +But for now we are using the solution provided by PHP-DI. +So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2] ?? []; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$invoker = $container->get(InvokerInterface::class); +assert($invoker instanceof InvokerInterface); +$response = $invoker->call($handler, $args); +assert($response instanceof ResponseInterface); +``` + +Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) +by php, and even some more. + +But let us move on to something more fun and add some templating functionality to our application as we are trying to build +a website in the end. + +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/app/data/pages/11-templating.md b/app/data/pages/11-templating.md new file mode 100644 index 0000000..3759664 --- /dev/null +++ b/app/data/pages/11-templating.md @@ -0,0 +1,240 @@ +[<< previous](10-invoker.md) | [next >>](12-configuration.md) + +### Templating + +A template engine is not necessary with PHP because the language itself can take care of that. But it can make things +like escaping values easier. They also make it easier to draw a clear line between your application logic and the +template files which should only put your variables into the HTML code. + +A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please +also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. +Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. + +For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install +that package before you continue (`composer require mustache/mustache`). + +Another well known alternative would be [Twig](http://twig.sensiolabs.org/). + +Now please go and have a look at the source code of the +[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class +does not implement an interface. + +You could just type hint against the concrete class. But the problem with this approach is that you create tight +coupling. + +In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the +implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want +to add functionality to the engine. You can't do that without going back and changing all your code that is tightly +coupled. + +What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need +another implementation, you just implement that interface in your new class and inject the new class instead. + +Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). +This sounds a lot more complicated than it is, so just follow along. + +First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). +This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. +A class can implement multiple interfaces if necessary. + +So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a +new folder in your `src/` folder with the name `Template` where you can put all the template related things. + +In there create a new interface `Renderer.php` that looks like this: + +```php + $data + * @return string + */ + public function render(string $template, array $data = []) : string; +} +``` + +Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file +`MustacheRenderer.php` with the following content: + +```php +engine->render($template, $data); + } +} +``` + +As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple +and only fulfills the interface. + +Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know +which implementation he has to inject when you hint for the interface. Add this line: + +```php +[ + ... + \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) + ->constructor(new Mustache_Engine), +] +``` + +Now update the Hello.php class to require an implementation of our renderer interface +and use that to render a string using mustache syntax. + + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + Renderer $renderer, + string $name = 'Stranger', + ): ResponseInterface { + $body = $response->getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render( + 'Hello {{name}}, the time is {{now}}!', + $data, + ); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} +``` + +Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. +But what we want is template files, so let's go back and change that. + +To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the +`dependencies.php` file and add the following code: + +```php +[ + ... + Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), + Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), +] +``` + +We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. +Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have +to rename all the template files. + +To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the +dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader +in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object +we can then use it in the factory. + +In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: + +``` +

Hello World

+Hello {{ name }} +``` + +Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` + +Navigate to the hello page in your browser to make sure everything works. + +One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies +file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration +values. + +Lets create a 'Settings' class in our './src' Folder: + +```php +addDefinitions([ + Settings::class => fn () => require __DIR__ '/settings.php', + ResponseInterface::class => create(Response::class), + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + Renderer::class => fn (ME $me) => new Mustache($me), + MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), +]); + +return $builder->build(); +``` + + + +And as always, don't forget to commit your changes. + + +[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/app/data/pages/12-configuration.md b/app/data/pages/12-configuration.md new file mode 100644 index 0000000..a44dfd5 --- /dev/null +++ b/app/data/pages/12-configuration.md @@ -0,0 +1,201 @@ +[<< previous](11-templating.md) | [next >>](13-refactoring.md) + +### Configuration + +In the last chapter we added some more definitions to our dependencies.php in that definitions +we needed to pass quite a few configuration settings and filesystem strings to the constructors +of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. + +As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. + +As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. + +Lets create a 'Settings' class in our './src' Folder: + +```php +filePath; + } +} +``` + +If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files +and craft a settings object from them. + +As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another +factory for our Container because everyone should have a Friend :) + +```php +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn () => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} +``` + +For this to work we need to change our dependencies.php file to just return the array of definitions: +And here we can instantly use the Settings object to create our template engine. + +```php + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), +]; +``` + +Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: +require __DIR__ . '/../vendor/autoload.php'; + +```php +... +error_reporting(E_ALL); + +$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); +$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); +... +``` + +Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. + +[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/app/data/pages/13-refactoring.md b/app/data/pages/13-refactoring.md new file mode 100644 index 0000000..067e168 --- /dev/null +++ b/app/data/pages/13-refactoring.md @@ -0,0 +1,377 @@ +[<< previous](12-configuration.md) | [next >>](14-middleware.md) + +### Refactoring + +By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no +reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. +After all the bootstrap file should just set up the classes needed for the handling logic and execute them. + +At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. +As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the +method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non +static and extracted an interface. + +'./src/Http/Emitter.php' +```php +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + echo $response->getBody(); + } +} +``` +After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following +code in the Bootstrap.php to emit the response: + +```php +/** @var Emitter $emitter */ +$emitter = $container->get(Emitter::class); +$emitter->emit($response); +``` + +If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily +write an adapter that implements your emitter interface and wraps that more advanced emitter + +Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and +calling the routerhandler that in the passes the request to a function and gets the response. + +For this to steps to be seperated we are going to create two more classes: +1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object +2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the + requestobject, fetches the correct object from the container and calls it to create a response. + +Lets create the HandlerInterface first: + +```php +getAttribute($this->routeAttributeName, false); + assert($handler !== false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} + +``` + +We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. +The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler +as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in +the next chapter. + +```php +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} +``` + +Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. +```php +[ + '...', + Emitter::class => fn (BasicEmitter $e) => $e, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, +], +``` + +And then we can update our Bootstrap.php to fetch all the services and let them handle the request. + +```php +... +$routeMiddleWare = $container->get(MiddlewareInterface::class); +assert($routeMiddleWare instanceof MiddlewareInterface); +$handler = $container->get(RoutedRequestHandler::class); +assert($handler instanceof RequestHandlerInterface); +$emitter = $container->get(Emitter::class); +assert($emitter instanceof Emitter); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$response = $routeMiddleWare->process($request, $handler); +$emitter->emit($response); +``` +Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of +code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). + +So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. + +I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class +should require to function properly. + +* A RequestFactory + We want our Kernel to be able to build the request itself +* An Emitter + Without an Emitter we will not be able to send the response to the client +* RouteMiddleware + To decore the request with the correct handler for the requested route +* RequestHandler + To delegate the request to the correct funtion that creates the response + +As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface +to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our +interface: + +```php +factory::fromGlobals(); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} +``` + +For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: + +```php +routeMiddleware->process($request, $this->handler); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} + +``` + +We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines + +```php +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); +``` + +You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking +at the Whoops output and adding the needed definitions to the dependencies.php file. + +And as always, don't forget to commit your changes. + +[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/app/data/pages/14-middleware.md b/app/data/pages/14-middleware.md new file mode 100644 index 0000000..e698327 --- /dev/null +++ b/app/data/pages/14-middleware.md @@ -0,0 +1,298 @@ +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) + +### Middleware + +In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain +a bit more about what this interface does and why it is used in many applications. + +The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets +passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all +the middlewars to the client/emitter. + +So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the +response after it gets created by our handlers. + +So lets take a look at the middleware and the requesthandler interfaces + +```php +interface MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; +} + +interface RequestHandlerInterface +{ + public function handle(ServerRequestInterface $request): ResponseInterface; +} +``` + +The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a +requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the +response. + +But the middleware could just ignore the handler and produce a response on its own as the interface just requires us +to produce a response. + +A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users +that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the +database. + +In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates +the request with an 'isAuthenticated' attribute. + +If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that +response is not already cached, than we let the handler create the response and store that in the cache for a few +seconds + +```php +interface CacheInterface +{ + public function get(string $key, callable $resolver, int $ttl): mixed; +} +``` + +The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one +defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses +the callable to produce the value and stores it for the time specified in ttl. + +so lets write our caching middleware: + +```php +final class CachingMiddleware implements MiddlewareInterface +{ + public function __construct(private CacheInterface $cache){} + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { + $key = $request->getUri()->getPath(); + return $this->cache->get($key, fn() => $handler->handle($request), 10); + } + return $handler->handle($request); + } +} +``` + +we can also modify the response after it has been created by our application, for example we could implement a gzip +middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser +know that our application is used to serve dank memes: + +```php +final class DankMemeMiddleware implements MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $response = $handler->handle($request); + return $response->withAddedHeader('Meme', 'Dank'); + } +} +``` + +but for our application we are going to just add two external middlewares: + +* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. +* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware + +```bash +composer require middlewares/trailing-slash +composer require middlewares/whoops +``` + +The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the +application as well as the middleware stack. + +Our desired request -> response flow looks something like this: + + Client + | ^ + v | + Kernel + | ^ + v | + Whoops Middleware + | ^ + v | + TrailingSlash + | ^ + v | + Routing + | ^ + v | + ContainerResolver + | ^ + v | + Controller/Action + +As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every +middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. + +```php +interface Pipeline +{ + public function dispatch(ServerRequestInterface $request): ResponseInterface; +} +``` + +And our implementation looks something like this: + +```php + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} +``` + +Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code +that should produce our response. + +In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument +and store that itself as the current tip. + +There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) + +Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline +Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline + +```php +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} +``` + +And configure the container to use the Factory to create the Pipeline: + +```php + ..., + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + ... +``` +And of course a new file called middlewares.php in our config folder: +```php +pipeline->dispatch($request); +} +``` + +Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our +pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a +dependency container, if you want you can do that as well. + +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) diff --git a/app/phpstan-baseline.neon b/app/phpstan-baseline.neon new file mode 100644 index 0000000..61697a1 --- /dev/null +++ b/app/phpstan-baseline.neon @@ -0,0 +1,7 @@ +parameters: + ignoreErrors: + - + message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" + count: 1 + path: src/Http/InvokerRoutedHandler.php + diff --git a/app/phpstan.neon b/app/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/app/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/app/public/css/spectre-exp.min.css b/app/public/css/spectre-exp.min.css new file mode 100644 index 0000000..d313774 --- /dev/null +++ b/app/public/css/spectre-exp.min.css @@ -0,0 +1 @@ +/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/app/public/css/spectre-icons.min.css b/app/public/css/spectre-icons.min.css new file mode 100644 index 0000000..0276f7b --- /dev/null +++ b/app/public/css/spectre-icons.min.css @@ -0,0 +1 @@ +/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/app/public/css/spectre.min.css b/app/public/css/spectre.min.css new file mode 100644 index 0000000..0fe23d9 --- /dev/null +++ b/app/public/css/spectre.min.css @@ -0,0 +1 @@ +/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/app/public/index.php b/app/public/index.php new file mode 100644 index 0000000..d93da3a --- /dev/null +++ b/app/public/index.php @@ -0,0 +1,5 @@ +getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render('hello', $data); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/app/src/Action/Other.php b/app/src/Action/Other.php new file mode 100644 index 0000000..895796e --- /dev/null +++ b/app/src/Action/Other.php @@ -0,0 +1,19 @@ +getBody(); + + $body->write('This works too!'); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/app/src/Action/Page.php b/app/src/Action/Page.php new file mode 100644 index 0000000..5d27e84 --- /dev/null +++ b/app/src/Action/Page.php @@ -0,0 +1,35 @@ +byTitle($page); + $content = $this->linkFilter($page->content); + $content = $parsedown->parse($content); + $html = $renderer->render('page', ['content' => $content]); + $response->getBody()->write($html); + return $response; + } + + private function linkFilter(string $content): string + { + $content = preg_replace('/\(\d\d-/m', '(', $content); + return str_replace('.md)', ')', $content); + } +} diff --git a/app/src/Bootstrap.php b/app/src/Bootstrap.php new file mode 100644 index 0000000..3abc2e5 --- /dev/null +++ b/app/src/Bootstrap.php @@ -0,0 +1,40 @@ +getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); diff --git a/app/src/Exception/InternalServerError.php b/app/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/app/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +factory::fromGlobals(); + } + + /** + * @param UriInterface|string $uri + * @param array $serverParams + */ + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} diff --git a/app/src/Factory/FileSystemSettingsProvider.php b/app/src/Factory/FileSystemSettingsProvider.php new file mode 100644 index 0000000..f071078 --- /dev/null +++ b/app/src/Factory/FileSystemSettingsProvider.php @@ -0,0 +1,22 @@ +filePath; + assert($settings instanceof Settings); + return $settings; + } +} diff --git a/app/src/Factory/PipelineProvider.php b/app/src/Factory/PipelineProvider.php new file mode 100644 index 0000000..77738f8 --- /dev/null +++ b/app/src/Factory/PipelineProvider.php @@ -0,0 +1,25 @@ +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} diff --git a/app/src/Factory/RequestFactory.php b/app/src/Factory/RequestFactory.php new file mode 100644 index 0000000..2b17abc --- /dev/null +++ b/app/src/Factory/RequestFactory.php @@ -0,0 +1,11 @@ +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn (): Settings => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} diff --git a/app/src/Factory/SettingsProvider.php b/app/src/Factory/SettingsProvider.php new file mode 100644 index 0000000..ce1c5f0 --- /dev/null +++ b/app/src/Factory/SettingsProvider.php @@ -0,0 +1,10 @@ +|class-string|callable $handler - */ - public function addRoute( - string $method, - string $path, - array|string|callable $handler, - ): self; -} \ No newline at end of file diff --git a/app/src/Http/BasicEmitter.php b/app/src/Http/BasicEmitter.php new file mode 100644 index 0000000..aefe088 --- /dev/null +++ b/app/src/Http/BasicEmitter.php @@ -0,0 +1,38 @@ +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + + echo $response->getBody(); + } +} diff --git a/app/src/Http/ContainerPipeline.php b/app/src/Http/ContainerPipeline.php new file mode 100644 index 0000000..816cedd --- /dev/null +++ b/app/src/Http/ContainerPipeline.php @@ -0,0 +1,82 @@ + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} diff --git a/app/src/Http/Emitter.php b/app/src/Http/Emitter.php new file mode 100644 index 0000000..ce4c035 --- /dev/null +++ b/app/src/Http/Emitter.php @@ -0,0 +1,10 @@ +getAttribute($this->routeAttributeName, false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} diff --git a/app/src/Http/Pipeline.php b/app/src/Http/Pipeline.php new file mode 100644 index 0000000..1a9dcda --- /dev/null +++ b/app/src/Http/Pipeline.php @@ -0,0 +1,11 @@ + $routes - */ - public function __construct( - private readonly ResponseFactoryInterface $responseFactory, - private readonly string $routeAttributeName = '__route_handler', - private array $routes = [], - private Dispatcher|null $dispatcher = null, - ) - { - } - - /** - * @throws MethodNotAllowed - * @throws NotFound - */ - private function decorateRequest(ServerRequestInterface $request): ServerRequestInterface - { - $this->dispatcher ??= $this->createDispatcher(); - $routeInfo = $this->dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath() - ); - - if ($routeInfo[0] === Dispatcher::NOT_FOUND) { - throw new NotFound; - } - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - - return $request->withAttribute($this->routeAttributeName, $routeInfo[1]); - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - - return $handler->handle($request); - } - - private function createDispatcher(): Dispatcher - { - return simpleDispatcher(function (RouteCollector $r) { - foreach ($this->routes as $route) { - $r->addRoute($route[0], $route[1], $route[2]); - } - }); - } - - /** - * @inheritDoc - */ - public function addRoute(string $method, string $path, array|string|callable $handler,): AddRoute - { - $this->routes[] = [$method, $path, $handler]; - return $this; - } -} diff --git a/app/src/Http/RouteMiddleware.php b/app/src/Http/RouteMiddleware.php new file mode 100644 index 0000000..e3df6f8 --- /dev/null +++ b/app/src/Http/RouteMiddleware.php @@ -0,0 +1,69 @@ +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} diff --git a/app/src/Http/RoutedRequestHandler.php b/app/src/Http/RoutedRequestHandler.php new file mode 100644 index 0000000..a7407c9 --- /dev/null +++ b/app/src/Http/RoutedRequestHandler.php @@ -0,0 +1,10 @@ +routeMiddleware->process($request, $this->handler); + return $this->pipeline->dispatch($request); } - public function run(ServerRequestInterface |null $request = null): void + public function run(): void { - $request ??= $this->createRequest(); + $request = $this->requestFactory->fromGlobals(); $response = $this->handle($request); $this->emitter->emit($response); } - - private function createRequest(): ServerRequestInterface - { - try { - $request = $this->container->get(ServerRequestInterface::class); - assert($request instanceof ServerRequestInterface); - return $request; - } catch (Throwable $t) { - throw new InternalServerError( - 'could not get Request from container, please configure the container ' . - 'in order to use run() wihtout a request', - $t->getCode(), - $t, - ); - } - } - - public function addRoute( - string $method, - string $path, - array|string|callable $handler): AddRoute - { - $this->routeMiddleware->addRoute($method, $path, $handler); - return $this; - } } diff --git a/app/src/Middleware/CacheMiddleware.php b/app/src/Middleware/CacheMiddleware.php new file mode 100644 index 0000000..482a057 --- /dev/null +++ b/app/src/Middleware/CacheMiddleware.php @@ -0,0 +1,33 @@ +getMethod() === 'GET') { + $key = (string) $request->getUri(); + $key = base64_encode($key); + $callback = fn () => $handler->handle($request); + $response = new Response(); + $body = $this->cache->get($key, function (ItemInterface $item) use ($callback) { + $item->expiresAfter(120); + return (string) $callback()->getBody(); + }); + $response->getBody()->write($body); + return $response; + } + return $handler->handle($request); + } +} diff --git a/app/src/Model/MarkdownPage.php b/app/src/Model/MarkdownPage.php new file mode 100644 index 0000000..503774f --- /dev/null +++ b/app/src/Model/MarkdownPage.php @@ -0,0 +1,13 @@ + $this->repo->all(); + return $this->cache->get('ALLPAGES', function (ItemInterface $item) use ($callback) { + $item->expiresAfter(30); + return $callback(); + }); + } + + public function byId(int $id): MarkdownPage + { + $callback = fn () => $this->repo->byId($id); + return $this->cache->get('PAGE' . $id, function (ItemInterface $item) use ($callback) { + $item->expiresAfter(30); + return $callback(); + }); + } + + public function byTitle(string $title): MarkdownPage + { + $callback = fn () => $this->repo->byTitle($title); + return $this->cache->get('PAGE' . $title, function (ItemInterface $item) use ($callback) { + $item->expiresAfter(30); + return $callback(); + }); + } +} diff --git a/app/src/Repository/MarkdownPageFilesystem.php b/app/src/Repository/MarkdownPageFilesystem.php new file mode 100644 index 0000000..abd4107 --- /dev/null +++ b/app/src/Repository/MarkdownPageFilesystem.php @@ -0,0 +1,63 @@ +dataPath . '*.md'); + assert(is_array($fileNames)); + return array_map(function (string $name): MarkdownPage { + usleep(100000); + $content = file_get_contents($name); + $name = str_replace($this->dataPath, '', $name); + $name = str_replace('.md', '', $name); + $id = (int) substr($name, 0, 2); + $title = substr($name, 3); + return new MarkdownPage($id, $title, $content); + }, $fileNames); + } + + public function byId(int $id): MarkdownPage + { + $callback = fn (MarkdownPage $p): bool => $p->id === $id; + $filtered = array_values(array_filter($this->all(), $callback)); + if (count($filtered) === 0) { + throw new NotFound; + } + return $filtered[0]; + } + + public function byTitle(string $title): MarkdownPage + { + $callback = fn (MarkdownPage $p): bool => $p->title === $title; + $filtered = array_values(array_filter($this->all(), $callback)); + if (count($filtered) === 0) { + throw new NotFound; + } + return $filtered[0]; + } +} diff --git a/app/src/Repository/MarkdownPageRepo.php b/app/src/Repository/MarkdownPageRepo.php new file mode 100644 index 0000000..b823af0 --- /dev/null +++ b/app/src/Repository/MarkdownPageRepo.php @@ -0,0 +1,17 @@ +engine->render($template, $data); + } +} diff --git a/app/src/Template/Renderer.php b/app/src/Template/Renderer.php new file mode 100644 index 0000000..ff916ed --- /dev/null +++ b/app/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data = []): string; +} diff --git a/app/templates/hello.html b/app/templates/hello.html new file mode 100644 index 0000000..15a4cd2 --- /dev/null +++ b/app/templates/hello.html @@ -0,0 +1,6 @@ +{{> partials/head }} +
+

Hello {{name}}

+

The time is {{now}}

+
+{{> partials/foot }} diff --git a/app/templates/page.html b/app/templates/page.html new file mode 100644 index 0000000..c3c5284 --- /dev/null +++ b/app/templates/page.html @@ -0,0 +1,5 @@ +{{> partials/head }} +
+ {{{content}}} +
+{{> partials/foot }} diff --git a/app/templates/partials/foot.html b/app/templates/partials/foot.html new file mode 100644 index 0000000..17c7245 --- /dev/null +++ b/app/templates/partials/foot.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/templates/partials/head.html b/app/templates/partials/head.html new file mode 100644 index 0000000..9d57085 --- /dev/null +++ b/app/templates/partials/head.html @@ -0,0 +1,11 @@ + + + + + Title + + + + + +
diff --git a/implementation/14-middleware/.php-cs-fixer.php b/implementation/14-middleware/.php-cs-fixer.php new file mode 100644 index 0000000..705a7d7 --- /dev/null +++ b/implementation/14-middleware/.php-cs-fixer.php @@ -0,0 +1,38 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config' + ]) + ); \ No newline at end of file diff --git a/implementation/14-middleware/.phpcs.xml.dist b/implementation/14-middleware/.phpcs.xml.dist new file mode 100644 index 0000000..3b433f6 --- /dev/null +++ b/implementation/14-middleware/.phpcs.xml.dist @@ -0,0 +1,9 @@ + + + + + src + config + + + \ No newline at end of file diff --git a/implementation/14-middleware/composer.json b/implementation/14-middleware/composer.json new file mode 100644 index 0000000..a1372b3 --- /dev/null +++ b/implementation/14-middleware/composer.json @@ -0,0 +1,50 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3", + "mustache/mustache": "^2.14", + "psr/http-server-middleware": "^1.0", + "middlewares/trailing-slash": "^2.0", + "middlewares/whoops": "^2.0" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require-dev": { + "phpstan/phpstan": "^1.5", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0", + "squizlabs/php_codesniffer": "^3.6", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.1", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "mnapoli/hard-mode": "^0.3.0", + "psalm/phar": "^4.22" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + } +} diff --git a/implementation/14-middleware/composer.lock b/implementation/14-middleware/composer.lock new file mode 100644 index 0000000..3ac6853 --- /dev/null +++ b/implementation/14-middleware/composer.lock @@ -0,0 +1,1815 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "580bbe25eceb19e89a36dc4e5541d44c", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "middlewares/trailing-slash", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/middlewares/trailing-slash.git", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", + "shasum": "" + }, + "require": { + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to normalize the trailing slash of the uri path", + "homepage": "https://github.com/middlewares/trailing-slash", + "keywords": [ + "http", + "middleware", + "normalize", + "path", + "psr-15", + "psr-7", + "slash" + ], + "support": { + "issues": "https://github.com/middlewares/trailing-slash/issues", + "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" + }, + "time": "2020-12-02T00:06:55+00:00" + }, + { + "name": "middlewares/utils", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/middlewares/utils.git", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v2.16", + "guzzlehttp/psr7": "^2.0", + "laminas/laminas-diactoros": "^2.4", + "nyholm/psr7": "^1.0", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "slim/psr7": "^1.4", + "squizlabs/php_codesniffer": "^3.5", + "sunrise/http-message": "^1.0", + "sunrise/http-server-request": "^1.0", + "sunrise/stream": "^1.0.15", + "sunrise/uri": "^1.0.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\Utils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Common utils for PSR-15 middleware packages", + "homepage": "https://github.com/middlewares/utils", + "keywords": [ + "PSR-11", + "http", + "middleware", + "psr-15", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/middlewares/utils/issues", + "source": "https://github.com/middlewares/utils/tree/v3.3.0" + }, + "time": "2021-07-04T17:56:23+00:00" + }, + { + "name": "middlewares/whoops", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/middlewares/whoops.git", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.5", + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^5.0 || ^7.0", + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to use Whoops as error handler", + "homepage": "https://github.com/middlewares/whoops", + "keywords": [ + "error", + "http", + "middleware", + "psr-15", + "psr-7", + "server", + "whoops" + ], + "support": { + "issues": "https://github.com/middlewares/whoops/issues", + "source": "https://github.com/middlewares/whoops/tree/v2.0.2" + }, + "time": "2022-01-27T20:31:30+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/master" + }, + "time": "2018-10-30T17:12:04+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "doctrine/coding-standard", + "version": "8.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/coding-standard.git", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Müller", + "email": "st.mueller@dzh-online.de" + } + ], + "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "doctrine", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/doctrine/coding-standard/issues", + "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" + }, + "time": "2021-04-03T10:54:55+00:00" + }, + { + "name": "mnapoli/hard-mode", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/hard-mode.git", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", + "shasum": "" + }, + "require": { + "doctrine/coding-standard": "^8.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Hard mode for PHP", + "support": { + "issues": "https://github.com/mnapoli/hard-mode/issues", + "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" + }, + "time": "2020-10-12T07:54:37+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.5.0" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-24T18:18:00+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^1.2.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" + }, + "time": "2021-11-18T09:30:29+00:00" + }, + { + "name": "psalm/phar", + "version": "4.22.0", + "source": { + "type": "git", + "url": "https://github.com/psalm/phar.git", + "reference": "feebed09c9782d9aaa819b794d880c2671ba0e4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/psalm/phar/zipball/feebed09c9782d9aaa819b794d880c2671ba0e4c", + "reference": "feebed09c9782d9aaa819b794d880c2671ba0e4c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "vimeo/psalm": "*" + }, + "bin": [ + "psalm.phar" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer-based Psalm Phar", + "support": { + "issues": "https://github.com/psalm/phar/issues", + "source": "https://github.com/psalm/phar/tree/4.22.0" + }, + "time": "2022-02-27T11:01:37+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/14-middleware/config/dependencies.php b/implementation/14-middleware/config/dependencies.php new file mode 100644 index 0000000..e84ad53 --- /dev/null +++ b/implementation/14-middleware/config/dependencies.php @@ -0,0 +1,42 @@ + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, + Emitter::class => fn (BasicEmitter $e) => $e, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), +]; diff --git a/implementation/14-middleware/config/middlewares.php b/implementation/14-middleware/config/middlewares.php new file mode 100644 index 0000000..71dd461 --- /dev/null +++ b/implementation/14-middleware/config/middlewares.php @@ -0,0 +1,11 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); + $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/implementation/14-middleware/config/settings.php b/implementation/14-middleware/config/settings.php new file mode 100644 index 0000000..b489400 --- /dev/null +++ b/implementation/14-middleware/config/settings.php @@ -0,0 +1,11 @@ +aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/14-middleware/public/index.php b/implementation/14-middleware/public/index.php new file mode 100644 index 0000000..d93da3a --- /dev/null +++ b/implementation/14-middleware/public/index.php @@ -0,0 +1,5 @@ +getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render('hello', $data); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/14-middleware/src/Action/Other.php b/implementation/14-middleware/src/Action/Other.php new file mode 100644 index 0000000..895796e --- /dev/null +++ b/implementation/14-middleware/src/Action/Other.php @@ -0,0 +1,19 @@ +getBody(); + + $body->write('This works too!'); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/14-middleware/src/Bootstrap.php b/implementation/14-middleware/src/Bootstrap.php new file mode 100644 index 0000000..3abc2e5 --- /dev/null +++ b/implementation/14-middleware/src/Bootstrap.php @@ -0,0 +1,40 @@ +getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); diff --git a/implementation/14-middleware/src/Exception/InternalServerError.php b/implementation/14-middleware/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/14-middleware/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +factory::fromGlobals(); + } + + /** + * @param UriInterface|string $uri + * @param array $serverParams + */ + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} diff --git a/implementation/14-middleware/src/Factory/FileSystemSettingsProvider.php b/implementation/14-middleware/src/Factory/FileSystemSettingsProvider.php new file mode 100644 index 0000000..f071078 --- /dev/null +++ b/implementation/14-middleware/src/Factory/FileSystemSettingsProvider.php @@ -0,0 +1,22 @@ +filePath; + assert($settings instanceof Settings); + return $settings; + } +} diff --git a/implementation/14-middleware/src/Factory/PipelineProvider.php b/implementation/14-middleware/src/Factory/PipelineProvider.php new file mode 100644 index 0000000..77738f8 --- /dev/null +++ b/implementation/14-middleware/src/Factory/PipelineProvider.php @@ -0,0 +1,25 @@ +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} diff --git a/implementation/14-middleware/src/Factory/RequestFactory.php b/implementation/14-middleware/src/Factory/RequestFactory.php new file mode 100644 index 0000000..2b17abc --- /dev/null +++ b/implementation/14-middleware/src/Factory/RequestFactory.php @@ -0,0 +1,11 @@ +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn (): Settings => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} diff --git a/implementation/14-middleware/src/Factory/SettingsProvider.php b/implementation/14-middleware/src/Factory/SettingsProvider.php new file mode 100644 index 0000000..ce1c5f0 --- /dev/null +++ b/implementation/14-middleware/src/Factory/SettingsProvider.php @@ -0,0 +1,10 @@ +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + + echo $response->getBody(); + } +} diff --git a/implementation/14-middleware/src/Http/ContainerPipeline.php b/implementation/14-middleware/src/Http/ContainerPipeline.php new file mode 100644 index 0000000..816cedd --- /dev/null +++ b/implementation/14-middleware/src/Http/ContainerPipeline.php @@ -0,0 +1,82 @@ + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} diff --git a/implementation/14-middleware/src/Http/Emitter.php b/implementation/14-middleware/src/Http/Emitter.php new file mode 100644 index 0000000..ce4c035 --- /dev/null +++ b/implementation/14-middleware/src/Http/Emitter.php @@ -0,0 +1,10 @@ +getAttribute($this->routeAttributeName, false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} diff --git a/implementation/14-middleware/src/Http/Pipeline.php b/implementation/14-middleware/src/Http/Pipeline.php new file mode 100644 index 0000000..1a9dcda --- /dev/null +++ b/implementation/14-middleware/src/Http/Pipeline.php @@ -0,0 +1,11 @@ +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} diff --git a/implementation/14-middleware/src/Http/RoutedRequestHandler.php b/implementation/14-middleware/src/Http/RoutedRequestHandler.php new file mode 100644 index 0000000..a7407c9 --- /dev/null +++ b/implementation/14-middleware/src/Http/RoutedRequestHandler.php @@ -0,0 +1,10 @@ +pipeline->dispatch($request); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} diff --git a/implementation/14-middleware/src/Service/Time/Now.php b/implementation/14-middleware/src/Service/Time/Now.php new file mode 100644 index 0000000..79224b3 --- /dev/null +++ b/implementation/14-middleware/src/Service/Time/Now.php @@ -0,0 +1,10 @@ +engine->render($template, $data); + } +} diff --git a/implementation/14-middleware/src/Template/Renderer.php b/implementation/14-middleware/src/Template/Renderer.php new file mode 100644 index 0000000..ff916ed --- /dev/null +++ b/implementation/14-middleware/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data = []): string; +} diff --git a/implementation/14-middleware/templates/hello.html b/implementation/14-middleware/templates/hello.html new file mode 100644 index 0000000..0e21f2a --- /dev/null +++ b/implementation/14-middleware/templates/hello.html @@ -0,0 +1,11 @@ + + + + + Hello World + + +

Hello {{name}}

+

The time is {{now}}

+ + \ No newline at end of file From ab3227b75fffa2a11a351d1f067834db85263b0b Mon Sep 17 00:00:00 2001 From: lubiana Date: Tue, 5 Apr 2022 00:02:52 +0200 Subject: [PATCH 120/314] update readme --- .gitignore | 5 +- README.md | 36 +- Vagrantfile | 1 - app/cli-config.php | 13 + app/composer.json | 11 +- app/composer.lock | 1742 ++++++++++++- app/config/dependencies.php | 25 +- app/config/middlewares.php | 3 +- app/config/routes.php | 3 +- app/config/settings.php | 15 +- app/src/Action/Page.php | 36 +- app/src/Factory/DoctrineEm.php | 32 + app/src/Factory/SettingsContainerProvider.php | 3 +- app/src/Middleware/CacheMiddleware.php | 22 +- app/src/Model/MarkdownPage.php | 16 +- app/src/Repository/CachedMarkdownPageRepo.php | 20 +- .../Repository/DoctrineMarkdownPageRepo.php | 60 + app/src/Repository/MarkdownPageFilesystem.php | 7 +- app/src/Repository/MarkdownPageRepo.php | 2 + app/src/Settings.php | 17 + app/templates/pagelist.html | 11 + app/templates/partials/head.html | 2 +- implementation/16-caching/.php-cs-fixer.php | 38 + implementation/16-caching/.phpcs.xml.dist | 9 + implementation/16-caching/composer.json | 54 + implementation/16-caching/composer.lock | 2273 +++++++++++++++++ .../16-caching/config/dependencies.php | 54 + .../16-caching/config/middlewares.php | 13 + implementation/16-caching/config/routes.php | 14 + implementation/16-caching/config/settings.php | 12 + .../data/pages/01-front-controller.md | 53 + .../16-caching/data/pages/02-composer.md | 75 + .../16-caching/data/pages/03-error-handler.md | 79 + .../data/pages/04-development-helpers.md | 260 ++ .../16-caching/data/pages/05-http.md | 124 + .../16-caching/data/pages/06-router.md | 101 + .../data/pages/07-dispatching-to-a-class.md | 137 + .../data/pages/08-inversion-of-control.md | 54 + .../data/pages/09-dependency-injector.md | 213 ++ .../16-caching/data/pages/10-invoker.md | 102 + .../16-caching/data/pages/11-templating.md | 240 ++ .../16-caching/data/pages/12-configuration.md | 201 ++ .../16-caching/data/pages/13-refactoring.md | 377 +++ .../16-caching/data/pages/14-middleware.md | 298 +++ .../16-caching/phpstan-baseline.neon | 7 + implementation/16-caching/phpstan.neon | 8 + .../16-caching/public/css/spectre-exp.min.css | 1 + .../public/css/spectre-icons.min.css | 1 + .../16-caching/public/css/spectre.min.css | 1 + implementation/16-caching/public/favicon.ico | Bin 0 -> 15086 bytes implementation/16-caching/public/index.php | 5 + implementation/16-caching/src/.gitkeep | 0 .../16-caching/src/Action/Hello.php | 31 + .../16-caching/src/Action/Other.php | 19 + implementation/16-caching/src/Action/Page.php | 35 + implementation/16-caching/src/Bootstrap.php | 40 + .../src/Exception/InternalServerError.php | 9 + .../src/Exception/MethodNotAllowed.php | 9 + .../16-caching/src/Exception/NotFound.php | 9 + .../src/Factory/ContainerProvider.php | 10 + .../src/Factory/DiactorosRequestFactory.php | 28 + .../Factory/FileSystemSettingsProvider.php | 22 + .../src/Factory/PipelineProvider.php | 25 + .../16-caching/src/Factory/RequestFactory.php | 11 + .../src/Factory/SettingsContainerProvider.php | 25 + .../src/Factory/SettingsProvider.php | 10 + .../16-caching/src/Http/BasicEmitter.php | 38 + .../16-caching/src/Http/ContainerPipeline.php | 82 + .../16-caching/src/Http/Emitter.php | 10 + .../src/Http/InvokerRoutedHandler.php | 34 + .../16-caching/src/Http/Pipeline.php | 11 + .../16-caching/src/Http/RouteMiddleware.php | 69 + .../src/Http/RoutedRequestHandler.php | 10 + implementation/16-caching/src/Kernel.php | 32 + .../src/Middleware/CacheMiddleware.php | 36 + .../16-caching/src/Model/MarkdownPage.php | 13 + .../src/Repository/CachedMarkdownPageRepo.php | 46 + .../src/Repository/MarkdownPageFilesystem.php | 63 + .../src/Repository/MarkdownPageRepo.php | 17 + .../16-caching/src/Service/Time/Now.php | 10 + .../src/Service/Time/SystemClockNow.php | 13 + implementation/16-caching/src/Settings.php | 16 + .../src/Template/MustacheRenderer.php | 17 + .../16-caching/src/Template/Renderer.php | 11 + .../16-caching/templates/hello.html | 6 + implementation/16-caching/templates/page.html | 5 + .../16-caching/templates/partials/foot.html | 3 + .../16-caching/templates/partials/head.html | 11 + 88 files changed, 7546 insertions(+), 176 deletions(-) create mode 100644 app/cli-config.php create mode 100644 app/src/Factory/DoctrineEm.php create mode 100644 app/src/Repository/DoctrineMarkdownPageRepo.php create mode 100644 app/templates/pagelist.html create mode 100644 implementation/16-caching/.php-cs-fixer.php create mode 100644 implementation/16-caching/.phpcs.xml.dist create mode 100644 implementation/16-caching/composer.json create mode 100644 implementation/16-caching/composer.lock create mode 100644 implementation/16-caching/config/dependencies.php create mode 100644 implementation/16-caching/config/middlewares.php create mode 100644 implementation/16-caching/config/routes.php create mode 100644 implementation/16-caching/config/settings.php create mode 100644 implementation/16-caching/data/pages/01-front-controller.md create mode 100644 implementation/16-caching/data/pages/02-composer.md create mode 100644 implementation/16-caching/data/pages/03-error-handler.md create mode 100644 implementation/16-caching/data/pages/04-development-helpers.md create mode 100644 implementation/16-caching/data/pages/05-http.md create mode 100644 implementation/16-caching/data/pages/06-router.md create mode 100644 implementation/16-caching/data/pages/07-dispatching-to-a-class.md create mode 100644 implementation/16-caching/data/pages/08-inversion-of-control.md create mode 100644 implementation/16-caching/data/pages/09-dependency-injector.md create mode 100644 implementation/16-caching/data/pages/10-invoker.md create mode 100644 implementation/16-caching/data/pages/11-templating.md create mode 100644 implementation/16-caching/data/pages/12-configuration.md create mode 100644 implementation/16-caching/data/pages/13-refactoring.md create mode 100644 implementation/16-caching/data/pages/14-middleware.md create mode 100644 implementation/16-caching/phpstan-baseline.neon create mode 100644 implementation/16-caching/phpstan.neon create mode 100644 implementation/16-caching/public/css/spectre-exp.min.css create mode 100644 implementation/16-caching/public/css/spectre-icons.min.css create mode 100644 implementation/16-caching/public/css/spectre.min.css create mode 100644 implementation/16-caching/public/favicon.ico create mode 100644 implementation/16-caching/public/index.php create mode 100644 implementation/16-caching/src/.gitkeep create mode 100644 implementation/16-caching/src/Action/Hello.php create mode 100644 implementation/16-caching/src/Action/Other.php create mode 100644 implementation/16-caching/src/Action/Page.php create mode 100644 implementation/16-caching/src/Bootstrap.php create mode 100644 implementation/16-caching/src/Exception/InternalServerError.php create mode 100644 implementation/16-caching/src/Exception/MethodNotAllowed.php create mode 100644 implementation/16-caching/src/Exception/NotFound.php create mode 100644 implementation/16-caching/src/Factory/ContainerProvider.php create mode 100644 implementation/16-caching/src/Factory/DiactorosRequestFactory.php create mode 100644 implementation/16-caching/src/Factory/FileSystemSettingsProvider.php create mode 100644 implementation/16-caching/src/Factory/PipelineProvider.php create mode 100644 implementation/16-caching/src/Factory/RequestFactory.php create mode 100644 implementation/16-caching/src/Factory/SettingsContainerProvider.php create mode 100644 implementation/16-caching/src/Factory/SettingsProvider.php create mode 100644 implementation/16-caching/src/Http/BasicEmitter.php create mode 100644 implementation/16-caching/src/Http/ContainerPipeline.php create mode 100644 implementation/16-caching/src/Http/Emitter.php create mode 100644 implementation/16-caching/src/Http/InvokerRoutedHandler.php create mode 100644 implementation/16-caching/src/Http/Pipeline.php create mode 100644 implementation/16-caching/src/Http/RouteMiddleware.php create mode 100644 implementation/16-caching/src/Http/RoutedRequestHandler.php create mode 100644 implementation/16-caching/src/Kernel.php create mode 100644 implementation/16-caching/src/Middleware/CacheMiddleware.php create mode 100644 implementation/16-caching/src/Model/MarkdownPage.php create mode 100644 implementation/16-caching/src/Repository/CachedMarkdownPageRepo.php create mode 100644 implementation/16-caching/src/Repository/MarkdownPageFilesystem.php create mode 100644 implementation/16-caching/src/Repository/MarkdownPageRepo.php create mode 100644 implementation/16-caching/src/Service/Time/Now.php create mode 100644 implementation/16-caching/src/Service/Time/SystemClockNow.php create mode 100644 implementation/16-caching/src/Settings.php create mode 100644 implementation/16-caching/src/Template/MustacheRenderer.php create mode 100644 implementation/16-caching/src/Template/Renderer.php create mode 100644 implementation/16-caching/templates/hello.html create mode 100644 implementation/16-caching/templates/page.html create mode 100644 implementation/16-caching/templates/partials/foot.html create mode 100644 implementation/16-caching/templates/partials/head.html diff --git a/.gitignore b/.gitignore index cc205bd..c84df77 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ **/vendor/ **/.php-cs-fixer.cache .idea/ -.vagrant/ \ No newline at end of file +.vagrant/ +/identifier.sqlite +*.log +**/*.sqlite diff --git a/README.md b/README.md index af7b810..a636596 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,37 @@ -# No Framework +# Create a PHP application without a Framework + +Hello and welcome to this tutorial with helps you in understanding how to write complex apps without the help of +a framework. This tutorial is not for people who have never written PHP before, you should at least have some +experience with object oriented PHP and be able to look at the official PHP-Documentation to figure out what +a function or class we are using does. + +I often hear people talking about frameworks as a solution to all the problems that you have in software development. +But in my opinion its even worse to use a framework if you do not know what you are doing, because often are fighting +more against the framework than actually solving the problem you should be working on. Even if you know what you are +doing i think it is good to get to know how the frameworks you are using work under the hood and what challenges they +actually solve for you. + +## Credit: + +This tutorial is based on the great [tutorial by Patrick Louys](https://github.com/PatrickLouys/no-framework-tutorial). +My version is way more opiniated and uses some newer PHP features. But you should still check out his tutorial which is +still very great and helped me personally a lot in taking the next step in my knowledge about PHP development. There is +also an [amazon book](https://patricklouys.com/professional-php/) which expands on the topics covered in this tutorial. + +## Getting started. + +As I am using a fairly new version of PHP in this tutorial I have added a Vagrantfile to this tutorial. If you do not +have PHP8.1 installed on your computer you can use the following commands to try out all the examples: + +```shell +vagrant up +vagrant ssh +cd app +``` + +I have exposed the port 1234 to be used in the VM, if you would like to use another one you are free to modify the +Vagrantfile. + + [Start](01-front-controller.md) diff --git a/Vagrantfile b/Vagrantfile index 0b30662..56a4db4 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -17,6 +17,5 @@ Vagrant.configure("2") do |config| echo -e 'zend_extension=xdebug\nxdebug.client_host=10.0.2.2\n' >> /etc/php/conf.d/tutorial.ini echo -e 'xdebug.client_port=9003\nxdebug.mode=debug\n' >> /etc/php/conf.d/tutorial.ini echo -e 'zend.assertions=1\n' >> /etc/php/conf.d/tutorial.ini - SHELL end diff --git a/app/cli-config.php b/app/cli-config.php new file mode 100644 index 0000000..fbc6598 --- /dev/null +++ b/app/cli-config.php @@ -0,0 +1,13 @@ +getContainer(); + +return ConsoleRunner::createHelperSet($container->get(EntityManagerInterface::class)); diff --git a/app/composer.json b/app/composer.json index 9cbc356..04edde0 100644 --- a/app/composer.json +++ b/app/composer.json @@ -12,7 +12,8 @@ "middlewares/trailing-slash": "^2.0", "middlewares/whoops": "^2.0", "erusev/parsedown": "^1.7", - "symfony/cache": "^6.0" + "symfony/cache": "^6.0", + "doctrine/orm": "^2.11" }, "autoload": { "psr-4": { @@ -33,8 +34,7 @@ "phpstan/extension-installer": "^1.1", "phpstan/phpstan-strict-rules": "^1.1", "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0", - "psalm/phar": "^4.22" + "mnapoli/hard-mode": "^0.3.0" }, "config": { "allow-plugins": { @@ -43,7 +43,10 @@ } }, "scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1234 -t public" + ], "phpstan": "./vendor/bin/phpstan analyze", "baseline": "./vendor/bin/phpstan analyze --generate-baseline", "check": "./vendor/bin/phpcs", diff --git a/app/composer.lock b/app/composer.lock index b10158c..904cfb4 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -4,8 +4,937 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0f81a412e25ca1269493dface2804540", + "content-hash": "a3762dd11bab0c9e948d3a73b7f252b9", "packages": [ + { + "name": "doctrine/cache", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/331b4d5dbaeab3827976273e9356b3b453c300ce", + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce", + "shasum": "" + }, + "require": { + "php": "~7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "alcaeus/mongo-php-adapter": "^1.1", + "cache/integration-tests": "dev-master", + "doctrine/coding-standard": "^8.0", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "predis/predis": "~1.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.2 || ^6.0@dev", + "symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", + "keywords": [ + "abstraction", + "apcu", + "cache", + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" + ], + "support": { + "issues": "https://github.com/doctrine/cache/issues", + "source": "https://github.com/doctrine/cache/tree/2.1.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "type": "tidelift" + } + ], + "time": "2021-07-17T14:49:29+00:00" + }, + { + "name": "doctrine/collections", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/1958a744696c6bb3bb0d28db2611dc11610e78af", + "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af", + "shasum": "" + }, + "require": { + "php": "^7.1.3 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5", + "vimeo/psalm": "^4.2.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", + "keywords": [ + "array", + "collections", + "iterators", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/collections/issues", + "source": "https://github.com/doctrine/collections/tree/1.6.8" + }, + "time": "2021-08-10T18:51:53+00:00" + }, + { + "name": "doctrine/common", + "version": "3.2.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "295082d3750987065912816a9d536c2df735f637" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/295082d3750987065912816a9d536c2df735f637", + "reference": "295082d3750987065912816a9d536c2df735f637", + "shasum": "" + }, + "require": { + "doctrine/persistence": "^2.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^1.4.1", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.0", + "symfony/phpunit-bridge": "^4.0.5", + "vimeo/psalm": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", + "homepage": "https://www.doctrine-project.org/projects/common.html", + "keywords": [ + "common", + "doctrine", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/common/issues", + "source": "https://github.com/doctrine/common/tree/3.2.2" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", + "type": "tidelift" + } + ], + "time": "2022-02-02T09:15:57+00:00" + }, + { + "name": "doctrine/dbal", + "version": "3.3.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "83f779beaea1893c0bece093ab2104c6d15a7f26" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/83f779beaea1893c0bece093ab2104c6d15a7f26", + "reference": "83f779beaea1893c0bece093ab2104c6d15a7f26", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/cache": "^1.11|^2.0", + "doctrine/deprecations": "^0.5.3", + "doctrine/event-manager": "^1.0", + "php": "^7.3 || ^8.0", + "psr/cache": "^1|^2|^3", + "psr/log": "^1|^2|^3" + }, + "require-dev": { + "doctrine/coding-standard": "9.0.0", + "jetbrains/phpstorm-stubs": "2021.1", + "phpstan/phpstan": "1.4.6", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "9.5.16", + "psalm/plugin-phpunit": "0.16.1", + "squizlabs/php_codesniffer": "3.6.2", + "symfony/cache": "^5.2|^6.0", + "symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0", + "vimeo/psalm": "4.22.0" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\DBAL\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", + "homepage": "https://www.doctrine-project.org/projects/dbal.html", + "keywords": [ + "abstraction", + "database", + "db2", + "dbal", + "mariadb", + "mssql", + "mysql", + "oci8", + "oracle", + "pdo", + "pgsql", + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlite", + "sqlserver", + "sqlsrv" + ], + "support": { + "issues": "https://github.com/doctrine/dbal/issues", + "source": "https://github.com/doctrine/dbal/tree/3.3.4" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", + "type": "tidelift" + } + ], + "time": "2022-03-20T18:37:29+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "v0.5.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "9504165960a1f83cc1480e2be1dd0a0478561314" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314", + "reference": "9504165960a1f83cc1480e2be1dd0a0478561314", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0|^7.0|^8.0", + "phpunit/phpunit": "^7.0|^8.0|^9.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v0.5.3" + }, + "time": "2021-03-21T12:59:47+00:00" + }, + { + "name": "doctrine/event-manager", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": "<2.9@dev" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/1.1.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2020-05-29T18:28:51+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "vimeo/psalm": "^4.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2021-10-22T20:16:43+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-03-03T08:28:38+00:00" + }, + { + "name": "doctrine/lexer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2022-02-28T11:07:21+00:00" + }, + { + "name": "doctrine/orm", + "version": "2.11.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/orm.git", + "reference": "9c351e044478135aec1755e2c0c0493a4b6309db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/orm/zipball/9c351e044478135aec1755e2c0c0493a4b6309db", + "reference": "9c351e044478135aec1755e2c0c0493a4b6309db", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/cache": "^1.12.1 || ^2.1.1", + "doctrine/collections": "^1.5", + "doctrine/common": "^3.0.3", + "doctrine/dbal": "^2.13.1 || ^3.2", + "doctrine/deprecations": "^0.5.3", + "doctrine/event-manager": "^1.1", + "doctrine/inflector": "^1.4 || ^2.0", + "doctrine/instantiator": "^1.3", + "doctrine/lexer": "^1.0", + "doctrine/persistence": "^2.2", + "ext-ctype": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3", + "symfony/console": "^3.0 || ^4.0 || ^5.0 || ^6.0", + "symfony/polyfill-php72": "^1.23", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "doctrine/annotations": "<1.13 || >= 2.0" + }, + "require-dev": { + "doctrine/annotations": "^1.13", + "doctrine/coding-standard": "^9.0", + "phpbench/phpbench": "^0.16.10 || ^1.0", + "phpstan/phpstan": "1.4.6", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "squizlabs/php_codesniffer": "3.6.2", + "symfony/cache": "^4.4 || ^5.4 || ^6.0", + "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0", + "vimeo/psalm": "4.22.0" + }, + "suggest": { + "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0", + "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" + }, + "bin": [ + "bin/doctrine" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\ORM\\": "lib/Doctrine/ORM" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "Object-Relational-Mapper for PHP", + "homepage": "https://www.doctrine-project.org/projects/orm.html", + "keywords": [ + "database", + "orm" + ], + "support": { + "issues": "https://github.com/doctrine/orm/issues", + "source": "https://github.com/doctrine/orm/tree/2.11.2" + }, + "time": "2022-03-09T15:23:58+00:00" + }, + { + "name": "doctrine/persistence", + "version": "2.4.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/persistence.git", + "reference": "092a52b71410ac1795287bb5135704ef07d18dd0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/092a52b71410ac1795287bb5135704ef07d18dd0", + "reference": "092a52b71410ac1795287bb5135704ef07d18dd0", + "shasum": "" + }, + "require": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/collections": "^1.0", + "doctrine/deprecations": "^0.5.3", + "doctrine/event-manager": "^1.0", + "php": "^7.1 || ^8.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0" + }, + "conflict": { + "doctrine/annotations": "<1.0 || >=2.0", + "doctrine/common": "<2.10" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.11", + "doctrine/annotations": "^1.0", + "doctrine/coding-standard": "^9.0", + "doctrine/common": "^3.0", + "phpstan/phpstan": "1.4.6", + "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6.0", + "vimeo/psalm": "4.21.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "src/Common", + "Doctrine\\Persistence\\": "src/Persistence" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", + "homepage": "https://doctrine-project.org/projects/persistence.html", + "keywords": [ + "mapper", + "object", + "odm", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/persistence/issues", + "source": "https://github.com/doctrine/persistence/tree/2.4.1" + }, + "time": "2022-03-22T06:44:40+00:00" + }, { "name": "erusev/parsedown", "version": "1.7.4", @@ -129,16 +1058,16 @@ }, { "name": "laminas/laminas-diactoros", - "version": "2.8.0", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", "shasum": "" }, "require": { @@ -224,7 +1153,7 @@ "type": "community_bridge" } ], - "time": "2021-09-22T03:54:36+00:00" + "time": "2022-03-29T20:12:16+00:00" }, { "name": "middlewares/trailing-slash", @@ -1278,6 +2207,101 @@ ], "time": "2021-08-17T15:35:52+00:00" }, + { + "name": "symfony/console", + "version": "v6.0.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "3bebf4108b9e07492a2a4057d207aa5a77d146b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/3bebf4108b9e07492a2a4057d207aa5a77d146b1", + "reference": "3bebf4108b9e07492a2a4057d207aa5a77d146b1", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.4|^6.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/lock": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.0.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-02-25T10:48:52+00:00" + }, { "name": "symfony/deprecation-contracts", "version": "v2.5.0", @@ -1345,6 +2369,495 @@ ], "time": "2021-07-12T14:48:14+00:00" }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "30885182c981ab175d4d034db0f6f469898070ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", + "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-10-20T20:35:02+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "81b86b50cf841a64252b439e738e97f4a34e2783" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", + "reference": "81b86b50cf841a64252b439e738e97f4a34e2783", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-23T21:10:46+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T09:17:38+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-04T08:16:47+00:00" + }, { "name": "symfony/service-contracts", "version": "v2.5.0", @@ -1428,6 +2941,91 @@ ], "time": "2021-11-04T16:48:04+00:00" }, + { + "name": "symfony/string", + "version": "v6.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/522144f0c4c004c80d56fa47e40e17028e2eefc2", + "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.0" + }, + "require-dev": { + "symfony/error-handler": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/translation-contracts": "^2.0|^3.0", + "symfony/var-exporter": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v6.0.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:55:41+00:00" + }, { "name": "symfony/var-exporter", "version": "v6.0.6", @@ -1808,16 +3406,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.5.0", + "version": "1.5.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" + "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/39953ac1452a8843702ee41a35b4861d3e8207a7", + "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7", "shasum": "" }, "require": { @@ -1843,7 +3441,7 @@ "description": "PHPStan - PHP Static Analysis Tool", "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.0" + "source": "https://github.com/phpstan/phpstan/tree/1.5.3" }, "funding": [ { @@ -1863,7 +3461,7 @@ "type": "tidelift" } ], - "time": "2022-03-24T18:18:00+00:00" + "time": "2022-03-30T21:55:08+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -1916,41 +3514,6 @@ }, "time": "2021-11-18T09:30:29+00:00" }, - { - "name": "psalm/phar", - "version": "4.22.0", - "source": { - "type": "git", - "url": "https://github.com/psalm/phar.git", - "reference": "feebed09c9782d9aaa819b794d880c2671ba0e4c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/psalm/phar/zipball/feebed09c9782d9aaa819b794d880c2671ba0e4c", - "reference": "feebed09c9782d9aaa819b794d880c2671ba0e4c", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "vimeo/psalm": "*" - }, - "bin": [ - "psalm.phar" - ], - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer-based Psalm Phar", - "support": { - "issues": "https://github.com/psalm/phar/issues", - "source": "https://github.com/psalm/phar/tree/4.22.0" - }, - "time": "2022-02-27T11:01:37+00:00" - }, { "name": "slevomat/coding-standard", "version": "6.4.1", @@ -2068,89 +3631,6 @@ }, "time": "2021-12-12T21:44:58+00:00" }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, { "name": "symfony/var-dumper", "version": "v6.0.6", @@ -2304,5 +3784,5 @@ "php": "^8.1" }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.3.0" } diff --git a/app/config/dependencies.php b/app/config/dependencies.php index e1ea3bf..4a97f31 100644 --- a/app/config/dependencies.php +++ b/app/config/dependencies.php @@ -1,8 +1,10 @@ fn (SystemClockNow $n) => $n, + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, + Emitter::class => fn (BasicEmitter $e) => $e, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, + CacheInterface::class => fn (FilesystemAdapter $a) => $a, + MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, + + // Factories ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, - Emitter::class => fn (BasicEmitter $e) => $e, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), - RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - CacheInterface::class => fn (FilesystemAdapter $a) => $a, MarkdownPageFilesystem::class => fn (Settings $s) => new MarkdownPageFilesystem($s->pagesPath), - CachedMarkdownPageRepo::class => fn (CacheInterface $c, MarkdownPageFilesystem $r) => new CachedMarkdownPageRepo($c, $r), - MarkdownPageRepo::class => fn (MarkdownPageFilesystem $r) => $r, + CachedMarkdownPageRepo::class => fn (CacheInterface $c, MarkdownPageFilesystem $r, Settings $s) => new CachedMarkdownPageRepo($c, $r, $s), + EntityManagerInterface::class => fn (DoctrineEm $f) => $f->create(), ]; diff --git a/app/config/middlewares.php b/app/config/middlewares.php index 891cc83..459547e 100644 --- a/app/config/middlewares.php +++ b/app/config/middlewares.php @@ -1,12 +1,13 @@ addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/page/{page}', Page::class); + $r->addRoute('GET', '/page', [Page::class, 'list']); + $r->addRoute('GET', '/page/{page}', [Page::class, 'show']); $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); }; diff --git a/app/config/settings.php b/app/config/settings.php index 8a0861d..5c58216 100644 --- a/app/config/settings.php +++ b/app/config/settings.php @@ -3,10 +3,21 @@ use Lubian\NoFramework\Settings; return new Settings( - environment: 'dev', + environment: 'prod', dependenciesFile: __DIR__ . '/dependencies.php', middlewaresFile: __DIR__ . '/middlewares.php', templateDir: __DIR__ . '/../templates', templateExtension: '.html', - pagesPath: __DIR__ . '/../data/pages/' + pagesPath: __DIR__ . '/../data/pages/', + connection: [ + 'driver' => 'pdo_sqlite', + 'user' => '', + 'password' => '', + 'path' => __DIR__ . '/../data/db.sqlite', + ], + doctrine: [ + 'devMode' => true, + 'metadataDirs' => [__DIR__ . '/../src/Model/'], + 'cacheDir' => __DIR__ . '/../data/cache/', + ], ); diff --git a/app/src/Action/Page.php b/app/src/Action/Page.php index 5d27e84..9fb86c2 100644 --- a/app/src/Action/Page.php +++ b/app/src/Action/Page.php @@ -2,6 +2,8 @@ namespace Lubian\NoFramework\Action; +use Lubian\NoFramework\Model\MarkdownPage; +use Lubian\NoFramework\Repository\MarkdownPageFilesystem; use Lubian\NoFramework\Repository\MarkdownPageRepo; use Lubian\NoFramework\Template\Renderer; use Parsedown; @@ -12,19 +14,33 @@ use function str_replace; class Page { - public function __invoke( + public function __construct( + private ResponseInterface $response, + private MarkdownPageRepo $repo, + private Parsedown $parsedown, + private Renderer $renderer, + ){} + public function show( string $page, - ResponseInterface $response, - MarkdownPageRepo $repo, - Parsedown $parsedown, - Renderer $renderer, ): ResponseInterface { - $page = $repo->byTitle($page); + $page = $this->repo->byTitle($page); $content = $this->linkFilter($page->content); - $content = $parsedown->parse($content); - $html = $renderer->render('page', ['content' => $content]); - $response->getBody()->write($html); - return $response; + $content = $this->parsedown->parse($content); + $html = $this->renderer->render('page', ['content' => $content, 'title' => $page->title]); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map( + fn (MarkdownPage $p) => ['title' => $p->title, 'id' => $p->id], + $this->repo->all() + ); + $html = $this->renderer->render('pagelist', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } private function linkFilter(string $content): string diff --git a/app/src/Factory/DoctrineEm.php b/app/src/Factory/DoctrineEm.php new file mode 100644 index 0000000..be24e7f --- /dev/null +++ b/app/src/Factory/DoctrineEm.php @@ -0,0 +1,32 @@ +settings->doctrine['devMode']); + + $config->setMetadataDriverImpl( + new AttributeDriver( + $this->settings->doctrine['metadataDirs'] + ) + ); + + return EntityManager::create( + $this->settings->connection, + $config, + ); + } +} \ No newline at end of file diff --git a/app/src/Factory/SettingsContainerProvider.php b/app/src/Factory/SettingsContainerProvider.php index ad6b0b0..baf278b 100644 --- a/app/src/Factory/SettingsContainerProvider.php +++ b/app/src/Factory/SettingsContainerProvider.php @@ -18,8 +18,9 @@ final class SettingsContainerProvider implements ContainerProvider $builder = new ContainerBuilder; $settings = $this->settingsProvider->getSettings(); $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn (): Settings => $settings; + $dependencies[Settings::class] = $settings; $builder->addDefinitions($dependencies); + // $builder->enableCompilation('/tmp'); return $builder->build(); } } diff --git a/app/src/Middleware/CacheMiddleware.php b/app/src/Middleware/CacheMiddleware.php index 482a057..2ff6f6e 100644 --- a/app/src/Middleware/CacheMiddleware.php +++ b/app/src/Middleware/CacheMiddleware.php @@ -3,6 +3,7 @@ namespace Lubian\NoFramework\Middleware; use Laminas\Diactoros\Response; +use Lubian\NoFramework\Settings; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; @@ -10,23 +11,30 @@ use Psr\Http\Server\RequestHandlerInterface; use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Cache\ItemInterface; +use function base64_encode; + final class CacheMiddleware implements MiddlewareInterface { - public function __construct(private CacheInterface $cache){} + public function __construct( + private CacheInterface $cache, + private Response\Serializer $serializer, + private Settings $settings, + ) + { + } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { - if ($request->getMethod() === 'GET') { + if ($request->getMethod() === 'GET' && !$this->settings->isDev()) { $key = (string) $request->getUri(); $key = base64_encode($key); $callback = fn () => $handler->handle($request); - $response = new Response(); - $body = $this->cache->get($key, function (ItemInterface $item) use ($callback) { + $cached = $this->cache->get($key, function (ItemInterface $item) use ($callback) { $item->expiresAfter(120); - return (string) $callback()->getBody(); + $response = $callback(); + return $this->serializer::toString($response); }); - $response->getBody()->write($body); - return $response; + return $this->serializer::fromString($cached); } return $handler->handle($request); } diff --git a/app/src/Model/MarkdownPage.php b/app/src/Model/MarkdownPage.php index 503774f..73f001d 100644 --- a/app/src/Model/MarkdownPage.php +++ b/app/src/Model/MarkdownPage.php @@ -2,12 +2,22 @@ namespace Lubian\NoFramework\Model; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\GeneratedValue; +use Doctrine\ORM\Mapping\Id; + +#[Entity] class MarkdownPage { public function __construct( - public readonly int $id, - public readonly string $title, - public readonly string $content, + #[Id, Column, GeneratedValue] + public int|null $id = null, + #[Column] + public string $title, + #[Column(type: Types::TEXT)] + public string $content, ) { } } diff --git a/app/src/Repository/CachedMarkdownPageRepo.php b/app/src/Repository/CachedMarkdownPageRepo.php index 9efdb17..1b4a1fb 100644 --- a/app/src/Repository/CachedMarkdownPageRepo.php +++ b/app/src/Repository/CachedMarkdownPageRepo.php @@ -3,14 +3,16 @@ namespace Lubian\NoFramework\Repository; use Lubian\NoFramework\Model\MarkdownPage; +use Lubian\NoFramework\Settings; use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Cache\ItemInterface; class CachedMarkdownPageRepo implements MarkdownPageRepo { public function __construct( - private CacheInterface $cache, - private MarkdownPageRepo $repo, + private readonly CacheInterface $cache, + private readonly MarkdownPageRepo $repo, + private readonly Settings $settings, ) { } @@ -20,6 +22,9 @@ class CachedMarkdownPageRepo implements MarkdownPageRepo public function all(): array { $callback = fn () => $this->repo->all(); + if ($this->settings->isDev()) { + return $callback(); + } return $this->cache->get('ALLPAGES', function (ItemInterface $item) use ($callback) { $item->expiresAfter(30); return $callback(); @@ -29,6 +34,9 @@ class CachedMarkdownPageRepo implements MarkdownPageRepo public function byId(int $id): MarkdownPage { $callback = fn () => $this->repo->byId($id); + if ($this->settings->isDev()) { + return $callback(); + } return $this->cache->get('PAGE' . $id, function (ItemInterface $item) use ($callback) { $item->expiresAfter(30); return $callback(); @@ -38,9 +46,17 @@ class CachedMarkdownPageRepo implements MarkdownPageRepo public function byTitle(string $title): MarkdownPage { $callback = fn () => $this->repo->byTitle($title); + if ($this->settings->isDev()) { + return $callback(); + } return $this->cache->get('PAGE' . $title, function (ItemInterface $item) use ($callback) { $item->expiresAfter(30); return $callback(); }); } + + public function save(MarkdownPage $page): MarkdownPage + { + return $this->repo->save($page); + } } diff --git a/app/src/Repository/DoctrineMarkdownPageRepo.php b/app/src/Repository/DoctrineMarkdownPageRepo.php new file mode 100644 index 0000000..f107c9e --- /dev/null +++ b/app/src/Repository/DoctrineMarkdownPageRepo.php @@ -0,0 +1,60 @@ + + */ + private EntityRepository $repo; + public function __construct( + private EntityManagerInterface $entityManager + ){ + $this->repo = $this->entityManager->getRepository(MarkdownPage::class); + } + + /** + * @inheritDoc + */ + public function all(): array + { + usleep(rand(500, 1500) * 1000); + return $this->repo->findAll(); + } + + public function byId(int $id): MarkdownPage + { + usleep(rand(500, 1500) * 1000); + $page = $this->repo->findOneBy(['id' => $id]); + if (!$page instanceof MarkdownPage){ + throw new NotFound; + } + return $page; + } + + public function byTitle(string $title): MarkdownPage + { + usleep(rand(500, 1500) * 1000); + $page = $this->repo->findOneBy(['title' => $title]); + if (!$page instanceof MarkdownPage){ + throw new NotFound; + } + return $page; + } + + public function save(MarkdownPage $page): MarkdownPage + { + $this->entityManager->persist($page); + $this->entityManager->flush(); + return $page; + } +} \ No newline at end of file diff --git a/app/src/Repository/MarkdownPageFilesystem.php b/app/src/Repository/MarkdownPageFilesystem.php index abd4107..1d70998 100644 --- a/app/src/Repository/MarkdownPageFilesystem.php +++ b/app/src/Repository/MarkdownPageFilesystem.php @@ -31,7 +31,7 @@ final class MarkdownPageFilesystem implements MarkdownPageRepo $fileNames = glob($this->dataPath . '*.md'); assert(is_array($fileNames)); return array_map(function (string $name): MarkdownPage { - usleep(100000); + usleep(rand(200, 500) * 1000); $content = file_get_contents($name); $name = str_replace($this->dataPath, '', $name); $name = str_replace('.md', '', $name); @@ -60,4 +60,9 @@ final class MarkdownPageFilesystem implements MarkdownPageRepo } return $filtered[0]; } + + public function save(MarkdownPage $page): MarkdownPage + { + return $page; + } } diff --git a/app/src/Repository/MarkdownPageRepo.php b/app/src/Repository/MarkdownPageRepo.php index b823af0..3f80899 100644 --- a/app/src/Repository/MarkdownPageRepo.php +++ b/app/src/Repository/MarkdownPageRepo.php @@ -14,4 +14,6 @@ interface MarkdownPageRepo public function byId(int $id): MarkdownPage; public function byTitle(string $title): MarkdownPage; + + public function save(MarkdownPage $page): MarkdownPage; } diff --git a/app/src/Settings.php b/app/src/Settings.php index 885aa7b..a6e4218 100644 --- a/app/src/Settings.php +++ b/app/src/Settings.php @@ -4,6 +4,10 @@ namespace Lubian\NoFramework; final class Settings { + /** + * @param array{driver: string, user: string, password: string, path: string} $connection + * @param array{devMode: bool, metadataDirs: string[], cacheDir: string} $doctrine + */ public function __construct( public readonly string $environment, public readonly string $dependenciesFile, @@ -11,6 +15,19 @@ final class Settings public readonly string $templateDir, public readonly string $templateExtension, public readonly string $pagesPath, + /** + * @var array{driver: string, user: string, password: string, path: string} + */ + public readonly array $connection, + /** + * @var array{devMode: bool, metadataDirs: string[], cacheDir: string} + */ + public readonly array $doctrine, ) { } + + public function isDev(): bool + { + return $this->environment === 'dev'; + } } diff --git a/app/templates/pagelist.html b/app/templates/pagelist.html new file mode 100644 index 0000000..538e2c4 --- /dev/null +++ b/app/templates/pagelist.html @@ -0,0 +1,11 @@ +{{> partials/head }} +
+ +
+{{> partials/foot }} diff --git a/app/templates/partials/head.html b/app/templates/partials/head.html index 9d57085..421d387 100644 --- a/app/templates/partials/head.html +++ b/app/templates/partials/head.html @@ -2,7 +2,7 @@ - Title + No Framework: {{title}} diff --git a/implementation/16-caching/.php-cs-fixer.php b/implementation/16-caching/.php-cs-fixer.php new file mode 100644 index 0000000..705a7d7 --- /dev/null +++ b/implementation/16-caching/.php-cs-fixer.php @@ -0,0 +1,38 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config' + ]) + ); \ No newline at end of file diff --git a/implementation/16-caching/.phpcs.xml.dist b/implementation/16-caching/.phpcs.xml.dist new file mode 100644 index 0000000..3b433f6 --- /dev/null +++ b/implementation/16-caching/.phpcs.xml.dist @@ -0,0 +1,9 @@ + + + + + src + config + + + \ No newline at end of file diff --git a/implementation/16-caching/composer.json b/implementation/16-caching/composer.json new file mode 100644 index 0000000..e85cc50 --- /dev/null +++ b/implementation/16-caching/composer.json @@ -0,0 +1,54 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3", + "mustache/mustache": "^2.14", + "psr/http-server-middleware": "^1.0", + "middlewares/trailing-slash": "^2.0", + "middlewares/whoops": "^2.0", + "erusev/parsedown": "^1.7", + "symfony/cache": "^6.0" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require-dev": { + "phpstan/phpstan": "^1.5", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0", + "squizlabs/php_codesniffer": "^3.6", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.1", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "mnapoli/hard-mode": "^0.3.0" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1234 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + } +} diff --git a/implementation/16-caching/composer.lock b/implementation/16-caching/composer.lock new file mode 100644 index 0000000..0c626d9 --- /dev/null +++ b/implementation/16-caching/composer.lock @@ -0,0 +1,2273 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "5286ff6a5dbbe21ace2a7359b49b8780", + "packages": [ + { + "name": "erusev/parsedown", + "version": "1.7.4", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "support": { + "issues": "https://github.com/erusev/parsedown/issues", + "source": "https://github.com/erusev/parsedown/tree/1.7.x" + }, + "time": "2019-12-30T22:54:17+00:00" + }, + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-03-29T20:12:16+00:00" + }, + { + "name": "middlewares/trailing-slash", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/middlewares/trailing-slash.git", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", + "shasum": "" + }, + "require": { + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to normalize the trailing slash of the uri path", + "homepage": "https://github.com/middlewares/trailing-slash", + "keywords": [ + "http", + "middleware", + "normalize", + "path", + "psr-15", + "psr-7", + "slash" + ], + "support": { + "issues": "https://github.com/middlewares/trailing-slash/issues", + "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" + }, + "time": "2020-12-02T00:06:55+00:00" + }, + { + "name": "middlewares/utils", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/middlewares/utils.git", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v2.16", + "guzzlehttp/psr7": "^2.0", + "laminas/laminas-diactoros": "^2.4", + "nyholm/psr7": "^1.0", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "slim/psr7": "^1.4", + "squizlabs/php_codesniffer": "^3.5", + "sunrise/http-message": "^1.0", + "sunrise/http-server-request": "^1.0", + "sunrise/stream": "^1.0.15", + "sunrise/uri": "^1.0.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\Utils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Common utils for PSR-15 middleware packages", + "homepage": "https://github.com/middlewares/utils", + "keywords": [ + "PSR-11", + "http", + "middleware", + "psr-15", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/middlewares/utils/issues", + "source": "https://github.com/middlewares/utils/tree/v3.3.0" + }, + "time": "2021-07-04T17:56:23+00:00" + }, + { + "name": "middlewares/whoops", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/middlewares/whoops.git", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.5", + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^5.0 || ^7.0", + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to use Whoops as error handler", + "homepage": "https://github.com/middlewares/whoops", + "keywords": [ + "error", + "http", + "middleware", + "psr-15", + "psr-7", + "server", + "whoops" + ], + "support": { + "issues": "https://github.com/middlewares/whoops/issues", + "source": "https://github.com/middlewares/whoops/tree/v2.0.2" + }, + "time": "2022-01-27T20:31:30+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/master" + }, + "time": "2018-10-30T17:12:04+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "symfony/cache", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", + "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^1.1.7|^2|^3", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/var-exporter": "^5.4|^6.0" + }, + "conflict": { + "doctrine/dbal": "<2.13.1", + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/var-dumper": "<5.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^2.13.1|^3.0", + "predis/predis": "^1.1", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/2f7463f156cf9c665d9317e21a809c3bbff5754e", + "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "psr/cache": "^3.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.0.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-08-17T15:35:52+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-07-12T14:48:14+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-04T16:48:04+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "130229a482abf17635a685590958894dfb4b4360" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/130229a482abf17635a685590958894dfb4b4360", + "reference": "130229a482abf17635a685590958894dfb4b4360", + "shasum": "" + }, + "require": { + "php": ">=8.0.2" + }, + "require-dev": { + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "doctrine/coding-standard", + "version": "8.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/coding-standard.git", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Müller", + "email": "st.mueller@dzh-online.de" + } + ], + "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "doctrine", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/doctrine/coding-standard/issues", + "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" + }, + "time": "2021-04-03T10:54:55+00:00" + }, + { + "name": "mnapoli/hard-mode", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/hard-mode.git", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", + "shasum": "" + }, + "require": { + "doctrine/coding-standard": "^8.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Hard mode for PHP", + "support": { + "issues": "https://github.com/mnapoli/hard-mode/issues", + "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" + }, + "time": "2020-10-12T07:54:37+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.5.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/39953ac1452a8843702ee41a35b4861d3e8207a7", + "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.5.3" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-30T21:55:08+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^1.2.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" + }, + "time": "2021-11-18T09:30:29+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/16-caching/config/dependencies.php b/implementation/16-caching/config/dependencies.php new file mode 100644 index 0000000..376aea0 --- /dev/null +++ b/implementation/16-caching/config/dependencies.php @@ -0,0 +1,54 @@ + fn (SystemClockNow $n) => $n, + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, + Emitter::class => fn (BasicEmitter $e) => $e, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, + CacheInterface::class => fn (FilesystemAdapter $a) => $a, + MarkdownPageRepo::class => fn (MarkdownPageFilesystem $r) => $r, + + // Factories + ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), + Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + MarkdownPageFilesystem::class => fn (Settings $s) => new MarkdownPageFilesystem($s->pagesPath), + CachedMarkdownPageRepo::class => fn (CacheInterface $c, MarkdownPageFilesystem $r) => new CachedMarkdownPageRepo($c, $r), +]; diff --git a/implementation/16-caching/config/middlewares.php b/implementation/16-caching/config/middlewares.php new file mode 100644 index 0000000..459547e --- /dev/null +++ b/implementation/16-caching/config/middlewares.php @@ -0,0 +1,13 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/page/{page}', Page::class); + $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); + $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/implementation/16-caching/config/settings.php b/implementation/16-caching/config/settings.php new file mode 100644 index 0000000..8a0861d --- /dev/null +++ b/implementation/16-caching/config/settings.php @@ -0,0 +1,12 @@ +>](02-composer.md) + +### Front Controller + +A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. + +To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. + +A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. + +The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. + +But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. + +So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. + +Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: + +```php +>](02-composer.md) diff --git a/implementation/16-caching/data/pages/02-composer.md b/implementation/16-caching/data/pages/02-composer.md new file mode 100644 index 0000000..a25a4a8 --- /dev/null +++ b/implementation/16-caching/data/pages/02-composer.md @@ -0,0 +1,75 @@ +[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) + +### Composer + +[Composer](https://getcomposer.org/) is a dependency manager for PHP. + +Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do +something. With Composer, you can install third-party libraries for your application. + +If you don't have Composer installed already, head over to the website and install it. You can find Composer packages +for your project on [Packagist](https://packagist.org/). + +Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will +be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. + +Add the following content to the file: + +```json +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubiana", + "email": "lubiana@hannover.ccc.de" + } + ] +} +``` + +In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use +whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. +Just replace it with your namespace in your own code. + +I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. + +As the Bootstrap.php file is placed in that directory we should +add the namespace to the File as well. Here is my current Bootstrap.php +as a reference: + +```php +>](03-error-handler.md) diff --git a/implementation/16-caching/data/pages/03-error-handler.md b/implementation/16-caching/data/pages/03-error-handler.md new file mode 100644 index 0000000..60465d0 --- /dev/null +++ b/implementation/16-caching/data/pages/03-error-handler.md @@ -0,0 +1,79 @@ +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) + +### Error Handler + +An error handler allows you to customize what happens if your code results in an error. + +A nice error page with a lot of information for debugging goes a long way during development. So the first package +for your application will take care of that. + +I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. +If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, +you have total control over your project. + +An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) + +To install a new package, open up your `composer.json` and add the package to the require part. It should now look +like this: + +```php +"require": { + "php": ">=8.1.0", + "filp/whoops": "^2.14" +}, +``` + +Now run `composer update` in your console and it will be installed. + +Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, +i that case composer automatically installs the package and updates your composer.json-file. + +But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, +ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you +only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. + +**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message +can help someone to gain access to your system. Always show a user friendly error page instead and send an email to +yourself, write to a log or something similar. So only you can see the errors in the production environment. + +For development that does not make sense though -- you want a nice error page. The solution is to have an environment +switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in +case no environment has been set. + +Then after the error handler registration, throw an `Exception` to test if everything is working correctly. +Your `Bootstrap.php` should now look similar to this: + +```php +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (\Throwable $e) { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +throw new \Exception("Ooooopsie"); + +``` + +You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until +you get it working. Now would also be a good time for another commit. + + +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/implementation/16-caching/data/pages/04-development-helpers.md b/implementation/16-caching/data/pages/04-development-helpers.md new file mode 100644 index 0000000..74f913c --- /dev/null +++ b/implementation/16-caching/data/pages/04-development-helpers.md @@ -0,0 +1,260 @@ +[<< previous](03-error-handler.md) | [next >>](05-http.md) + +### Development Helpers + +I have added some more helpers to my composer.json that help me with development. As these are scripts and programms +used only for development they should not be used in a production environment. Composer has a specific sections in its +file called "dev-dependencies", everything that is required in this section does not get installen in production. + +Let's install our dev-helpers and i will explain them one by one: +`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` + +#### Static Code Analysis with phpstan + +Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or +create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements +that are not (or not always) available, if-statements that always are true and a lot of other stuff. + +A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. + +```php +/** + * @param \DateTime $date + * @return void + */ +function printDate($date) { + $date->format('Y-m-d H:i:s'); +} + +printDate('now'); +``` +if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` + +It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has +no use, and secondly, that we are calling the function with a string instead of a datetime object. + +```shell +1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + ------ --------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ --------------------------------------------------------------------------------------------- +30 Call to method DateTime::format() on a separate line has no effect. +33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. + ------ --------------------------------------------------------------------------------------------- +``` + +The second error is something that "declare strict-types" already catches for us, but the first error is something that +we usually would not discover easily without speccially looking for this errortype. + +We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and +path everytime we want to check our code for errors: + +```yaml +parameters: + level: max + paths: + - src +``` +now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project + +With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us +some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. + +```shell +composer require --dev phpstan/extension-installer +composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules +``` + +During the first install you need to allow the extension installer to actually install the extension. The second command +installs some more strict rulesets and activates them in phpstan. + +If we now rerun phpstan it already tells us about some errors we have made: + +``` + ------ ----------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ ----------------------------------------------------------------------------------------------- +10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider + using long ternary. +25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: + http://bit.ly/subtypeexception +26 Unreachable statement - code above always terminates. + ------ ----------------------------------------------------------------------------------------------- +``` + +The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove +that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific +problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working +on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages +everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we +are adding to our code. + +In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the +phpstan.neon file and run phpstan with the +'--generate-baseline' option: + +```yaml +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` +```shell +[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline +Note: Using configuration file /home/vagrant/app/phpstan.neon. + 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + + + [OK] Baseline generated with 1 error. + + +``` + +you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) + +#### PHP-CS-Fixer + +Another great tool is the php-cs-fixer, which just applies a specific style to your code. + +when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current +directory. + +You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) + +personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup +a configuration file that i use in all my projects .php-cs-fixer.php: + +```php +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + ]) + ); +``` + +#### PHP Codesniffer + +The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra +rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. + +it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. + +Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have +guessed we are adding some extra rules. + +Lets install the ruleset with composer +```shell +composer require --dev mnapoli/hard-mode +``` + +and add a configuration file to actually use it '.phpcs.xml.dist' +```xml + + + + + src + + + +``` + +running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. + +``` +[vagrant@archlinux app]$ ./vendor/bin/phpcs + +FILE: src/Bootstrap.php +---------------------------------------------------------------------------------------------------- +FOUND 4 ERRORS AFFECTING 4 LINES +---------------------------------------------------------------------------------------------------- + 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. + 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead +---------------------------------------------------------------------------------------------------- +PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY +---------------------------------------------------------------------------------------------------- + +Time: 639ms; Memory: 10MB +``` + +You can then use `./vendor/bin/phpcbf` to try to fix them + + +#### Symfony Var-Dumper + +another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small +functions. + +dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects +or arrays. + +dd() on the other hand is a function that dumps its parameters and then exits the php-script. + +you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. + +#### Composer scripts + +now we have a few commands that are available on the command line. i personally do not like to type complex commands +with lots of parameters by hand all the time, so i added a few lines to my composer.json: + +```json +"scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" +}, +``` + +that way i can just type "composer" followed by the command name in the root of my project. if i want to start the +php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the +time. + +You could also configure PhpStorm to automatically run these commands in the background and highlight the violations +directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my +flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. + +My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before +commiting and pushing the code. + +[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/implementation/16-caching/data/pages/05-http.md b/implementation/16-caching/data/pages/05-http.md new file mode 100644 index 0000000..6166214 --- /dev/null +++ b/implementation/16-caching/data/pages/05-http.md @@ -0,0 +1,124 @@ +[<< previous](04-development-helpers.md) | [next >>](06-router.md) + +### HTTP + +PHP already has a few things built in to make working with HTTP easier. For example there are the +[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. + +These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, +if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, +then you will want a class with a nice object-oriented interface that you can use in your application instead. + +Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The +standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php +projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. + +As this is a widely adopted standard there are already several implementations available for us to use. I will choose +the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. + +Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a +[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. + +Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use +that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding +the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). + + +to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit +enter + +Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): + +```php +$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); +$response = new \Laminas\Diactoros\Response; +$response->getBody()->write('Hello World! '); +$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); +``` + +This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. + +In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the +write()-Method on that object. + + +To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: + +```php +echo $response->getBody(); +``` + +This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only +stores data. + +You can play around with the other methods of the Request object and take a look at its content with the dd() function. + +```php +dd($response) +``` + +Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot +be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which +creates a copy of the request object with the changed property and returns that clone: + +```php +$response = $response->withStatus(200); +$response = $response->withAddedHeader('Content-type', 'application/json'); +``` + +If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been +defined this way. + +But if you have been keeping attention you might argue that the following line should not work if the request object is +immutable. + +```php +$response->getBody()->write('Hello World!'); +``` + +The response-body implements a stream interface which is immutable for some reasons that are described in the +[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be +aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in +the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. +I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content +to a response object. + +```php +$body = $response->getBody(); +$body->write('Hello World!'); +$response = $response->withBody($body); +``` + +Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our +output-logic a little bit more. Replace the line that echos the response-body with the following: + +```php +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); +``` + +This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a +webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. + +Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. + +Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter + + +[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/implementation/16-caching/data/pages/06-router.md b/implementation/16-caching/data/pages/06-router.md new file mode 100644 index 0000000..6c39ae5 --- /dev/null +++ b/implementation/16-caching/data/pages/06-router.md @@ -0,0 +1,101 @@ +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) + +### Router + +A router dispatches to different handlers depending on rules that you have set up. + +With your current setup it does not matter what URL is used to access the application, it will always result in the same +response. So let's fix that now. + +I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own +favorite package. + +Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) + +By now you know how to install Composer packages, so I will leave that to you. + +Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. + +```php +$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +switch ($routeInfo[0]) { + case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: + $response = (new \Laminas\Diactoros\Response)->withStatus(405); + $response->getBody()->write('Method not allowed'); + $response = $response->withStatus(405); + break; + case \FastRoute\Dispatcher::FOUND: + $handler = $routeInfo[1]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + /** @var \Psr\Http\Message\ResponseInterface $response */ + $response = call_user_func($handler, $request); + break; + case \FastRoute\Dispatcher::NOT_FOUND: + default: + $response = (new \Laminas\Diactoros\Response)->withStatus(404); + $response->getBody()->write('Not Found!'); + break; +} +``` + +In the first part of the code, you are registering the available routes for your application. In the second part, the +dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, +we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. +If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. + +This setup might work for really small applications, but once you start adding a few routes your bootstrap file will +quickly get cluttered. So let's move them out into a separate file. + +Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; + +```php +addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}; +``` + +Now let's rewrite the route dispatcher part to use the `Routes.php` file. + +```php +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); +``` + +This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. + +Of course we now need to add the 'config' folder to the configuration files of our +devhelpers so that they can scan that directory as well. + +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/implementation/16-caching/data/pages/07-dispatching-to-a-class.md b/implementation/16-caching/data/pages/07-dispatching-to-a-class.md new file mode 100644 index 0000000..0c961a4 --- /dev/null +++ b/implementation/16-caching/data/pages/07-dispatching-to-a-class.md @@ -0,0 +1,137 @@ +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) + +### Dispatching to a Class + +In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). +MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to +learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) +and the followup posts. + +So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). + +We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other +common names are 'Controllers' or 'Actions'. + +Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. +In there, create a `Hello.php` file. + +```php +getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + } +} +``` + +You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) +that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is +fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement +it in some other parts of our application as well. In order to use that Interface we have to require it with composer: +'composer require psr/http-server-handler'. + +The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. +At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. + +Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: + +```php +return function(\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); + $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); +}; +``` + +Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared +the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing +the response to the one we defined for the second route. + +To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: + +```php +case \FastRoute\Dispatcher::FOUND: + $handler = new $routeInfo[1]; + if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { + throw new \Exception('Invalid Requesthandler'); + } + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + assert($response instanceof \Psr\Http\Message\ResponseInterface) + break; +``` + +So instead of just calling a method you are now instantiating an object and then calling the method on it. + +Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. + +And of course don't forget to commit your changes. + +Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still +generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for +example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still +have our whoopsie error-handler but i like to be more explicit in my control flow. + +In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new +Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and +InternalServerError. All three should extend phps Base Exception class. + +Here is my NotFound.php for example. + +```php + $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody()->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} +``` + +Check if our code still works, try to trigger some errors, run phpstan and the fix command +and don't forget to commit your changes. + +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/implementation/16-caching/data/pages/08-inversion-of-control.md b/implementation/16-caching/data/pages/08-inversion-of-control.md new file mode 100644 index 0000000..21f4f23 --- /dev/null +++ b/implementation/16-caching/data/pages/08-inversion-of-control.md @@ -0,0 +1,54 @@ +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) + +### Inversion of Control + +In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we +want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then +we would need to edit every one of our request handlers to call a different constructor of the class. + +The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that +instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done +with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). + +If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is +implemented, it will make sense. + +Change your `Hello` action to the following: + +```php +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: + +```php +$handler = new $className($response); +``` + +Of course we need to also update all the other handlers. + +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/implementation/16-caching/data/pages/09-dependency-injector.md b/implementation/16-caching/data/pages/09-dependency-injector.md new file mode 100644 index 0000000..7f7c6a2 --- /dev/null +++ b/implementation/16-caching/data/pages/09-dependency-injector.md @@ -0,0 +1,213 @@ +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) + +### Dependency Injector + +A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when +the class is instantiated. + +Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work +with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look +for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). + +I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) +out of the box. + +After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: + +```php +addDefinitions([ + \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), +]); + +return $builder->build(); +``` + +In this file we create a containerbuilder, add some definitions to it and return the container. +As the container supports autowiring we only need to define services where we want to use a specific implementation of +an interface. + +In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to +tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second +exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. + +Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), +as we will use that extensively. + +Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally +to create our Handler-Object + +```php +$container = require __DIR__ . '/../config/dependencies.php'; +assert($container instanceof \Psr\Container\ContainerInterface); + +$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); +assert($request instanceof \Psr\Http\Message\ServerRequestInterface); +``` + +The other part that has to be changed is the dispatching of the route. Before you had the following code: + +```php +$className = $routeInfo[1]; +$handler = new $className($response); +assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Change that to the following: + +```php +/** @var RequestHandlerInterface $handler */ +$className = $routeInfo[1]; +$handler = $container->get($className); +assert($handler instanceof RequestHandlerInterface); +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Make sure to use the container fetch the response object in the catch blocks as well: + +```php +} catch (MethodNotAllowed) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(404); + $response->getBody()->write('Not Found'); +} +``` + +Now all your controller constructor dependencies will be automatically resolved with PHP-DI. + +We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons +in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call +`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we +want to work with a different date in a test. + +Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation +that creates us a DateTimeImmutable object with the current date and time. + +src/Service/Time/Now.php: +```php +namespace Lubian\NoFramework\Service\Time; + +interface Now +{ + public function __invoke(): \DateTimeImmutable; +} +``` +src/Service/Time/SystemClockNow.php: +```php +namespace Lubian\NoFramework\Service\Time; + +final class SystemClockNow implements Now +{ + + public function __invoke(): \DateTimeImmutable + { + return new \DateTimeImmutable; + } +} +``` +If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and +update the handle-method to use the new class property: + +```php +getAttribute('name', 'Stranger'); + $nowAsString = ($this->now)()->format('H:i:s'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowAsString); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI +automatically figures out what classes are requested in the constructor and tries to create the objects needed. + +But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred +S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: + +```php + public function __construct( + private ResponseInterface $response, + private Now $now, + ) +``` + +When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation +should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: + +```php +\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), +``` + +we could also use the PHP-DI create method to delegate the object creation to the container implementation: +```php +\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), +``` + +this way the container can try to resolve any dependencies that the class might have internally, but prefer the other +method because we are not depending on this specific dependency injection implementation. + +Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are +requesting the Hello action. + +If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As +we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way +we wont get any warnings for this particular issue, but for any other issues we add to our code. + +Update the phpstan.neon file to include a "baseline" file: + +``` +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` + +if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and +ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just +'baseline' + +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/implementation/16-caching/data/pages/10-invoker.md b/implementation/16-caching/data/pages/10-invoker.md new file mode 100644 index 0000000..3033fae --- /dev/null +++ b/implementation/16-caching/data/pages/10-invoker.md @@ -0,0 +1,102 @@ +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) + +### Invoker + +Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the +one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long +with some services and not the whole Requestobject with all its various properties. + +If we take our Hello action for example we only need a response object, the time service and the 'name' information from +the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named +the class hello and it would be redundant to also call the the method hello. So an updated version of that class could +look like this: + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + string $name = 'Stranger', + ): ResponseInterface + { + $body = $this->response->getBody(); + $nowString = $now->get()->format('H:i:s'); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowString); + return $response + ->withBody($body) + ->withStatus(200); + } +} +``` + +It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short +closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the +rootpath of our application yet. + +```php +$r->addRoute('GET', '/hello[/{name}]', Hello::class); +$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); +$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +``` + +In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the +route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that +class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what +arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router +if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple +callable we would need to manually use reflection as well to resolve all the arguments. + +But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) +which handles all of that for us. + +After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo +switch section in the Bootstrap.php file to use the container->call() method: + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2]; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$response = $container->call($handler, $args); +``` + +Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. + +But by now you should know that I do not like to depend on specific implementations and the call method is not defined in +the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container +or any other implementation. + +Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific +container implementation so we could use it with any container that implements the ContainerInterface. And best of all +the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that +we could implement if we ever want to write our own implementation or we could write an adapter that uses a different +class that solves the same problem. + +But for now we are using the solution provided by PHP-DI. +So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2] ?? []; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$invoker = $container->get(InvokerInterface::class); +assert($invoker instanceof InvokerInterface); +$response = $invoker->call($handler, $args); +assert($response instanceof ResponseInterface); +``` + +Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) +by php, and even some more. + +But let us move on to something more fun and add some templating functionality to our application as we are trying to build +a website in the end. + +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/implementation/16-caching/data/pages/11-templating.md b/implementation/16-caching/data/pages/11-templating.md new file mode 100644 index 0000000..3759664 --- /dev/null +++ b/implementation/16-caching/data/pages/11-templating.md @@ -0,0 +1,240 @@ +[<< previous](10-invoker.md) | [next >>](12-configuration.md) + +### Templating + +A template engine is not necessary with PHP because the language itself can take care of that. But it can make things +like escaping values easier. They also make it easier to draw a clear line between your application logic and the +template files which should only put your variables into the HTML code. + +A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please +also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. +Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. + +For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install +that package before you continue (`composer require mustache/mustache`). + +Another well known alternative would be [Twig](http://twig.sensiolabs.org/). + +Now please go and have a look at the source code of the +[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class +does not implement an interface. + +You could just type hint against the concrete class. But the problem with this approach is that you create tight +coupling. + +In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the +implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want +to add functionality to the engine. You can't do that without going back and changing all your code that is tightly +coupled. + +What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need +another implementation, you just implement that interface in your new class and inject the new class instead. + +Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). +This sounds a lot more complicated than it is, so just follow along. + +First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). +This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. +A class can implement multiple interfaces if necessary. + +So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a +new folder in your `src/` folder with the name `Template` where you can put all the template related things. + +In there create a new interface `Renderer.php` that looks like this: + +```php + $data + * @return string + */ + public function render(string $template, array $data = []) : string; +} +``` + +Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file +`MustacheRenderer.php` with the following content: + +```php +engine->render($template, $data); + } +} +``` + +As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple +and only fulfills the interface. + +Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know +which implementation he has to inject when you hint for the interface. Add this line: + +```php +[ + ... + \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) + ->constructor(new Mustache_Engine), +] +``` + +Now update the Hello.php class to require an implementation of our renderer interface +and use that to render a string using mustache syntax. + + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + Renderer $renderer, + string $name = 'Stranger', + ): ResponseInterface { + $body = $response->getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render( + 'Hello {{name}}, the time is {{now}}!', + $data, + ); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} +``` + +Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. +But what we want is template files, so let's go back and change that. + +To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the +`dependencies.php` file and add the following code: + +```php +[ + ... + Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), + Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), +] +``` + +We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. +Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have +to rename all the template files. + +To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the +dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader +in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object +we can then use it in the factory. + +In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: + +``` +

Hello World

+Hello {{ name }} +``` + +Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` + +Navigate to the hello page in your browser to make sure everything works. + +One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies +file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration +values. + +Lets create a 'Settings' class in our './src' Folder: + +```php +addDefinitions([ + Settings::class => fn () => require __DIR__ '/settings.php', + ResponseInterface::class => create(Response::class), + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + Renderer::class => fn (ME $me) => new Mustache($me), + MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), +]); + +return $builder->build(); +``` + + + +And as always, don't forget to commit your changes. + + +[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/implementation/16-caching/data/pages/12-configuration.md b/implementation/16-caching/data/pages/12-configuration.md new file mode 100644 index 0000000..a44dfd5 --- /dev/null +++ b/implementation/16-caching/data/pages/12-configuration.md @@ -0,0 +1,201 @@ +[<< previous](11-templating.md) | [next >>](13-refactoring.md) + +### Configuration + +In the last chapter we added some more definitions to our dependencies.php in that definitions +we needed to pass quite a few configuration settings and filesystem strings to the constructors +of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. + +As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. + +As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. + +Lets create a 'Settings' class in our './src' Folder: + +```php +filePath; + } +} +``` + +If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files +and craft a settings object from them. + +As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another +factory for our Container because everyone should have a Friend :) + +```php +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn () => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} +``` + +For this to work we need to change our dependencies.php file to just return the array of definitions: +And here we can instantly use the Settings object to create our template engine. + +```php + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), +]; +``` + +Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: +require __DIR__ . '/../vendor/autoload.php'; + +```php +... +error_reporting(E_ALL); + +$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); +$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); +... +``` + +Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. + +[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/implementation/16-caching/data/pages/13-refactoring.md b/implementation/16-caching/data/pages/13-refactoring.md new file mode 100644 index 0000000..067e168 --- /dev/null +++ b/implementation/16-caching/data/pages/13-refactoring.md @@ -0,0 +1,377 @@ +[<< previous](12-configuration.md) | [next >>](14-middleware.md) + +### Refactoring + +By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no +reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. +After all the bootstrap file should just set up the classes needed for the handling logic and execute them. + +At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. +As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the +method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non +static and extracted an interface. + +'./src/Http/Emitter.php' +```php +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + echo $response->getBody(); + } +} +``` +After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following +code in the Bootstrap.php to emit the response: + +```php +/** @var Emitter $emitter */ +$emitter = $container->get(Emitter::class); +$emitter->emit($response); +``` + +If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily +write an adapter that implements your emitter interface and wraps that more advanced emitter + +Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and +calling the routerhandler that in the passes the request to a function and gets the response. + +For this to steps to be seperated we are going to create two more classes: +1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object +2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the + requestobject, fetches the correct object from the container and calls it to create a response. + +Lets create the HandlerInterface first: + +```php +getAttribute($this->routeAttributeName, false); + assert($handler !== false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} + +``` + +We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. +The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler +as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in +the next chapter. + +```php +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} +``` + +Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. +```php +[ + '...', + Emitter::class => fn (BasicEmitter $e) => $e, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, +], +``` + +And then we can update our Bootstrap.php to fetch all the services and let them handle the request. + +```php +... +$routeMiddleWare = $container->get(MiddlewareInterface::class); +assert($routeMiddleWare instanceof MiddlewareInterface); +$handler = $container->get(RoutedRequestHandler::class); +assert($handler instanceof RequestHandlerInterface); +$emitter = $container->get(Emitter::class); +assert($emitter instanceof Emitter); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$response = $routeMiddleWare->process($request, $handler); +$emitter->emit($response); +``` +Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of +code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). + +So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. + +I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class +should require to function properly. + +* A RequestFactory + We want our Kernel to be able to build the request itself +* An Emitter + Without an Emitter we will not be able to send the response to the client +* RouteMiddleware + To decore the request with the correct handler for the requested route +* RequestHandler + To delegate the request to the correct funtion that creates the response + +As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface +to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our +interface: + +```php +factory::fromGlobals(); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} +``` + +For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: + +```php +routeMiddleware->process($request, $this->handler); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} + +``` + +We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines + +```php +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); +``` + +You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking +at the Whoops output and adding the needed definitions to the dependencies.php file. + +And as always, don't forget to commit your changes. + +[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/implementation/16-caching/data/pages/14-middleware.md b/implementation/16-caching/data/pages/14-middleware.md new file mode 100644 index 0000000..e698327 --- /dev/null +++ b/implementation/16-caching/data/pages/14-middleware.md @@ -0,0 +1,298 @@ +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) + +### Middleware + +In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain +a bit more about what this interface does and why it is used in many applications. + +The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets +passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all +the middlewars to the client/emitter. + +So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the +response after it gets created by our handlers. + +So lets take a look at the middleware and the requesthandler interfaces + +```php +interface MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; +} + +interface RequestHandlerInterface +{ + public function handle(ServerRequestInterface $request): ResponseInterface; +} +``` + +The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a +requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the +response. + +But the middleware could just ignore the handler and produce a response on its own as the interface just requires us +to produce a response. + +A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users +that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the +database. + +In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates +the request with an 'isAuthenticated' attribute. + +If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that +response is not already cached, than we let the handler create the response and store that in the cache for a few +seconds + +```php +interface CacheInterface +{ + public function get(string $key, callable $resolver, int $ttl): mixed; +} +``` + +The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one +defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses +the callable to produce the value and stores it for the time specified in ttl. + +so lets write our caching middleware: + +```php +final class CachingMiddleware implements MiddlewareInterface +{ + public function __construct(private CacheInterface $cache){} + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { + $key = $request->getUri()->getPath(); + return $this->cache->get($key, fn() => $handler->handle($request), 10); + } + return $handler->handle($request); + } +} +``` + +we can also modify the response after it has been created by our application, for example we could implement a gzip +middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser +know that our application is used to serve dank memes: + +```php +final class DankMemeMiddleware implements MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $response = $handler->handle($request); + return $response->withAddedHeader('Meme', 'Dank'); + } +} +``` + +but for our application we are going to just add two external middlewares: + +* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. +* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware + +```bash +composer require middlewares/trailing-slash +composer require middlewares/whoops +``` + +The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the +application as well as the middleware stack. + +Our desired request -> response flow looks something like this: + + Client + | ^ + v | + Kernel + | ^ + v | + Whoops Middleware + | ^ + v | + TrailingSlash + | ^ + v | + Routing + | ^ + v | + ContainerResolver + | ^ + v | + Controller/Action + +As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every +middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. + +```php +interface Pipeline +{ + public function dispatch(ServerRequestInterface $request): ResponseInterface; +} +``` + +And our implementation looks something like this: + +```php + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} +``` + +Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code +that should produce our response. + +In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument +and store that itself as the current tip. + +There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) + +Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline +Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline + +```php +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} +``` + +And configure the container to use the Factory to create the Pipeline: + +```php + ..., + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + ... +``` +And of course a new file called middlewares.php in our config folder: +```php +pipeline->dispatch($request); +} +``` + +Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our +pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a +dependency container, if you want you can do that as well. + +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) diff --git a/implementation/16-caching/phpstan-baseline.neon b/implementation/16-caching/phpstan-baseline.neon new file mode 100644 index 0000000..61697a1 --- /dev/null +++ b/implementation/16-caching/phpstan-baseline.neon @@ -0,0 +1,7 @@ +parameters: + ignoreErrors: + - + message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" + count: 1 + path: src/Http/InvokerRoutedHandler.php + diff --git a/implementation/16-caching/phpstan.neon b/implementation/16-caching/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/16-caching/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/16-caching/public/css/spectre-exp.min.css b/implementation/16-caching/public/css/spectre-exp.min.css new file mode 100644 index 0000000..d313774 --- /dev/null +++ b/implementation/16-caching/public/css/spectre-exp.min.css @@ -0,0 +1 @@ +/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/implementation/16-caching/public/css/spectre-icons.min.css b/implementation/16-caching/public/css/spectre-icons.min.css new file mode 100644 index 0000000..0276f7b --- /dev/null +++ b/implementation/16-caching/public/css/spectre-icons.min.css @@ -0,0 +1 @@ +/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/implementation/16-caching/public/css/spectre.min.css b/implementation/16-caching/public/css/spectre.min.css new file mode 100644 index 0000000..0fe23d9 --- /dev/null +++ b/implementation/16-caching/public/css/spectre.min.css @@ -0,0 +1 @@ +/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/implementation/16-caching/public/favicon.ico b/implementation/16-caching/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/16-caching/public/index.php b/implementation/16-caching/public/index.php new file mode 100644 index 0000000..d93da3a --- /dev/null +++ b/implementation/16-caching/public/index.php @@ -0,0 +1,5 @@ +getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render('hello', $data); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/16-caching/src/Action/Other.php b/implementation/16-caching/src/Action/Other.php new file mode 100644 index 0000000..895796e --- /dev/null +++ b/implementation/16-caching/src/Action/Other.php @@ -0,0 +1,19 @@ +getBody(); + + $body->write('This works too!'); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/16-caching/src/Action/Page.php b/implementation/16-caching/src/Action/Page.php new file mode 100644 index 0000000..e3461ad --- /dev/null +++ b/implementation/16-caching/src/Action/Page.php @@ -0,0 +1,35 @@ +byTitle($page); + $content = $this->linkFilter($page->content); + $content = $parsedown->parse($content); + $html = $renderer->render('page', ['content' => $content, 'title' => $page->title]); + $response->getBody()->write($html); + return $response; + } + + private function linkFilter(string $content): string + { + $content = preg_replace('/\(\d\d-/m', '(', $content); + return str_replace('.md)', ')', $content); + } +} diff --git a/implementation/16-caching/src/Bootstrap.php b/implementation/16-caching/src/Bootstrap.php new file mode 100644 index 0000000..3abc2e5 --- /dev/null +++ b/implementation/16-caching/src/Bootstrap.php @@ -0,0 +1,40 @@ +getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); diff --git a/implementation/16-caching/src/Exception/InternalServerError.php b/implementation/16-caching/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/16-caching/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +factory::fromGlobals(); + } + + /** + * @param UriInterface|string $uri + * @param array $serverParams + */ + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} diff --git a/implementation/16-caching/src/Factory/FileSystemSettingsProvider.php b/implementation/16-caching/src/Factory/FileSystemSettingsProvider.php new file mode 100644 index 0000000..f071078 --- /dev/null +++ b/implementation/16-caching/src/Factory/FileSystemSettingsProvider.php @@ -0,0 +1,22 @@ +filePath; + assert($settings instanceof Settings); + return $settings; + } +} diff --git a/implementation/16-caching/src/Factory/PipelineProvider.php b/implementation/16-caching/src/Factory/PipelineProvider.php new file mode 100644 index 0000000..77738f8 --- /dev/null +++ b/implementation/16-caching/src/Factory/PipelineProvider.php @@ -0,0 +1,25 @@ +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} diff --git a/implementation/16-caching/src/Factory/RequestFactory.php b/implementation/16-caching/src/Factory/RequestFactory.php new file mode 100644 index 0000000..2b17abc --- /dev/null +++ b/implementation/16-caching/src/Factory/RequestFactory.php @@ -0,0 +1,11 @@ +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn (): Settings => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} diff --git a/implementation/16-caching/src/Factory/SettingsProvider.php b/implementation/16-caching/src/Factory/SettingsProvider.php new file mode 100644 index 0000000..ce1c5f0 --- /dev/null +++ b/implementation/16-caching/src/Factory/SettingsProvider.php @@ -0,0 +1,10 @@ +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + + echo $response->getBody(); + } +} diff --git a/implementation/16-caching/src/Http/ContainerPipeline.php b/implementation/16-caching/src/Http/ContainerPipeline.php new file mode 100644 index 0000000..816cedd --- /dev/null +++ b/implementation/16-caching/src/Http/ContainerPipeline.php @@ -0,0 +1,82 @@ + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} diff --git a/implementation/16-caching/src/Http/Emitter.php b/implementation/16-caching/src/Http/Emitter.php new file mode 100644 index 0000000..ce4c035 --- /dev/null +++ b/implementation/16-caching/src/Http/Emitter.php @@ -0,0 +1,10 @@ +getAttribute($this->routeAttributeName, false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} diff --git a/implementation/16-caching/src/Http/Pipeline.php b/implementation/16-caching/src/Http/Pipeline.php new file mode 100644 index 0000000..1a9dcda --- /dev/null +++ b/implementation/16-caching/src/Http/Pipeline.php @@ -0,0 +1,11 @@ +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} diff --git a/implementation/16-caching/src/Http/RoutedRequestHandler.php b/implementation/16-caching/src/Http/RoutedRequestHandler.php new file mode 100644 index 0000000..a7407c9 --- /dev/null +++ b/implementation/16-caching/src/Http/RoutedRequestHandler.php @@ -0,0 +1,10 @@ +pipeline->dispatch($request); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} diff --git a/implementation/16-caching/src/Middleware/CacheMiddleware.php b/implementation/16-caching/src/Middleware/CacheMiddleware.php new file mode 100644 index 0000000..edf67d7 --- /dev/null +++ b/implementation/16-caching/src/Middleware/CacheMiddleware.php @@ -0,0 +1,36 @@ +getMethod() === 'GET') { + $key = (string) $request->getUri(); + $key = base64_encode($key); + $callback = fn () => $handler->handle($request); + $cached = $this->cache->get($key, function (ItemInterface $item) use ($callback) { + $item->expiresAfter(120); + $response = $callback(); + return $this->serializer::toString($response); + }); + return $this->serializer::fromString($cached); + } + return $handler->handle($request); + } +} diff --git a/implementation/16-caching/src/Model/MarkdownPage.php b/implementation/16-caching/src/Model/MarkdownPage.php new file mode 100644 index 0000000..503774f --- /dev/null +++ b/implementation/16-caching/src/Model/MarkdownPage.php @@ -0,0 +1,13 @@ + $this->repo->all(); + return $this->cache->get('ALLPAGES', function (ItemInterface $item) use ($callback) { + $item->expiresAfter(30); + return $callback(); + }); + } + + public function byId(int $id): MarkdownPage + { + $callback = fn () => $this->repo->byId($id); + return $this->cache->get('PAGE' . $id, function (ItemInterface $item) use ($callback) { + $item->expiresAfter(30); + return $callback(); + }); + } + + public function byTitle(string $title): MarkdownPage + { + $callback = fn () => $this->repo->byTitle($title); + return $this->cache->get('PAGE' . $title, function (ItemInterface $item) use ($callback) { + $item->expiresAfter(30); + return $callback(); + }); + } +} diff --git a/implementation/16-caching/src/Repository/MarkdownPageFilesystem.php b/implementation/16-caching/src/Repository/MarkdownPageFilesystem.php new file mode 100644 index 0000000..abd4107 --- /dev/null +++ b/implementation/16-caching/src/Repository/MarkdownPageFilesystem.php @@ -0,0 +1,63 @@ +dataPath . '*.md'); + assert(is_array($fileNames)); + return array_map(function (string $name): MarkdownPage { + usleep(100000); + $content = file_get_contents($name); + $name = str_replace($this->dataPath, '', $name); + $name = str_replace('.md', '', $name); + $id = (int) substr($name, 0, 2); + $title = substr($name, 3); + return new MarkdownPage($id, $title, $content); + }, $fileNames); + } + + public function byId(int $id): MarkdownPage + { + $callback = fn (MarkdownPage $p): bool => $p->id === $id; + $filtered = array_values(array_filter($this->all(), $callback)); + if (count($filtered) === 0) { + throw new NotFound; + } + return $filtered[0]; + } + + public function byTitle(string $title): MarkdownPage + { + $callback = fn (MarkdownPage $p): bool => $p->title === $title; + $filtered = array_values(array_filter($this->all(), $callback)); + if (count($filtered) === 0) { + throw new NotFound; + } + return $filtered[0]; + } +} diff --git a/implementation/16-caching/src/Repository/MarkdownPageRepo.php b/implementation/16-caching/src/Repository/MarkdownPageRepo.php new file mode 100644 index 0000000..b823af0 --- /dev/null +++ b/implementation/16-caching/src/Repository/MarkdownPageRepo.php @@ -0,0 +1,17 @@ +engine->render($template, $data); + } +} diff --git a/implementation/16-caching/src/Template/Renderer.php b/implementation/16-caching/src/Template/Renderer.php new file mode 100644 index 0000000..ff916ed --- /dev/null +++ b/implementation/16-caching/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data = []): string; +} diff --git a/implementation/16-caching/templates/hello.html b/implementation/16-caching/templates/hello.html new file mode 100644 index 0000000..15a4cd2 --- /dev/null +++ b/implementation/16-caching/templates/hello.html @@ -0,0 +1,6 @@ +{{> partials/head }} +
+

Hello {{name}}

+

The time is {{now}}

+
+{{> partials/foot }} diff --git a/implementation/16-caching/templates/page.html b/implementation/16-caching/templates/page.html new file mode 100644 index 0000000..c3c5284 --- /dev/null +++ b/implementation/16-caching/templates/page.html @@ -0,0 +1,5 @@ +{{> partials/head }} +
+ {{{content}}} +
+{{> partials/foot }} diff --git a/implementation/16-caching/templates/partials/foot.html b/implementation/16-caching/templates/partials/foot.html new file mode 100644 index 0000000..17c7245 --- /dev/null +++ b/implementation/16-caching/templates/partials/foot.html @@ -0,0 +1,3 @@ +
+ + \ No newline at end of file diff --git a/implementation/16-caching/templates/partials/head.html b/implementation/16-caching/templates/partials/head.html new file mode 100644 index 0000000..421d387 --- /dev/null +++ b/implementation/16-caching/templates/partials/head.html @@ -0,0 +1,11 @@ + + + + + No Framework: {{title}} + + + + + +
From eb20213b940774a8eed8b3a43f6d4ac46e251da1 Mon Sep 17 00:00:00 2001 From: lubiana Date: Tue, 5 Apr 2022 19:09:40 +0200 Subject: [PATCH 121/314] add 'adding content' chapter --- 14-middleware.md | 4 +- 15-adding-content.md | 248 ++++++++++ app/composer.json | 3 +- app/composer.lock | 460 +++++++++++++++++- app/config/dependencies.php | 4 +- app/config/middlewares.php | 2 - app/src/Action/Other.php | 13 +- app/src/Action/Page.php | 69 ++- app/src/Factory/DoctrineEm.php | 12 +- app/src/Factory/SettingsContainerProvider.php | 2 +- app/src/Middleware/CacheMiddleware.php | 5 +- app/src/Model/MarkdownPage.php | 10 +- .../Repository/DoctrineMarkdownPageRepo.php | 27 +- app/src/Repository/MarkdownPageFilesystem.php | 3 +- app/src/Template/GithubMarkdownRenderer.php | 28 ++ app/src/Template/MarkdownParser.php | 8 + app/src/Template/ParsedownParser.php | 17 + app/templates/page/list.html | 19 + app/templates/page/show.html | 13 + implementation/02-composer/composer.lock | 188 +------ 20 files changed, 884 insertions(+), 251 deletions(-) create mode 100644 15-adding-content.md create mode 100644 app/src/Template/GithubMarkdownRenderer.php create mode 100644 app/src/Template/MarkdownParser.php create mode 100644 app/src/Template/ParsedownParser.php create mode 100644 app/templates/page/list.html create mode 100644 app/templates/page/show.html diff --git a/14-middleware.md b/14-middleware.md index e698327..087e26b 100644 --- a/14-middleware.md +++ b/14-middleware.md @@ -1,4 +1,4 @@ -[<< previous](12-refactoring.md) | [next >>](14-invoker.md) +[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) ### Middleware @@ -295,4 +295,4 @@ Lets try if you can make the kernel work with our created Pipeline implementatio pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a dependency container, if you want you can do that as well. -[<< previous](12-refactoring.md) | [next >>](14-invoker.md) +[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) diff --git a/15-adding-content.md b/15-adding-content.md new file mode 100644 index 0000000..d1a7348 --- /dev/null +++ b/15-adding-content.md @@ -0,0 +1,248 @@ +[<< previous](14-middleware.md) | [next >>](14-invoker.md) + +### Adding Content + +By now we did not really display anything but some examples to in our application and it is now time to make our app +display some content. For example we could our app be able to display the Markdown files used in this tutorial as +nicely rendered HTML Pages that can be viewed in the browser instead of the editor you are using. + +So lets start by copying the markdown files to our app directory. I have created a new folder 'data/pages' and placed all +the markdown files in there. + +Next we need a markdown parser, a pretty simple one is [Parsedown](https://parsedown.org/), if you want more features +you could also use the [Commonmark parser](https://commonmark.thephpleague.com/). I will choose Parsedown here, but you +can use whatever you like. + +After installing Parsedown lets write a Markdownparser interface and an implementation using parsedown. + +We only need one function that receives a string of Markdown and returns the HTML represantion (as a string as well). + + + +```php +parser->parse($markdown); + } +} +``` + +We could now use the ParsedownRender class directly in our actions by typehinting the classname as an argument to the +constructor or a method, but as we always want to rely on an interface instead of an implementation we need to define +the the ParsedownRenderer as the correct implementation for the MarkdownRenderer interface in the dependencies file: + +```php +... + \Lubian\NoFramework\Template\MarkdownParser::class => fn(\Lubian\NoFramework\Template\ParsedownParser $p) => $p, +... +``` + +You can test that in our "Other.php" action and try out if the Parser works and is able to render Markdown to HTML: + +```php +public function someFunctionName(ResponseInterface $response, MarkdownParser $parser): ResponseInterface +{ + $html = $parser->parse('This *works* **too!**'); + $response->getBody()->write($html); + return $response->withStatus(200); +} +``` + +But we want to display complete Pages written in Markdown, it would also be neat to be able to display a list of all +available pages. For that we need a few things: + +Firstly we need two new Templates, one for the list of the Pages, and the second one for displaying a single pages +content. Create a new folder in `templates/page` with to files: + +`templates/page/list.html` +```html + + + + + Pages + + + +
+ +
+ + +``` + +This template iterates over a provided array of pages, each element consists of the two properties: an id and a title, +those are simply displayed using an unordered list. + +`templates/page/show.html` +```html + + + + + {{title}} + + + +
+ {{{content}}} +
+ + +``` + +The second templates displays a single rendered markdown page. As data it expects the title and the content as array. +I used an extra bracket for the content ```{{{content}}}``` so that the Mustache-Renderer does not escape the provided +html and thereby destroys the the parsed markdown. + +You might have spotted that I added [Pico.css](https://picocss.com/) which is just a very small css framework to make the +pages a little bit nicer to look at. It mostly provides some typography styles that work great with rendered Markdown, +but you can leave that out or use any other css framework you like. + +After you have taken care of the templating side we can now create an new Action class with two methods to display use +our markdown files and the templates to create the pages. As we have two templates I propose to use Two methods in our +Action: +`src/Action/Page.php` +```php +function show(string $name): \Psr\Http\Message\ResponseInterface; +function list(): \Psr\Http\Message\ResponseInterface; +``` + +Lets define two routes. `/page` should display the overview of all pages, and if the add the name of chapter to the +route, `/page/adding-content` for example, the show action should be called with the name as a variable: + +`config/routes.php` +```php +$r->addRoute('GET', '/page', [Page::class, 'list']); +$r->addRoute('GET', '/page/{page}', [Page::class, 'show']); +``` + +Here is my Implementation. If have added a little regex replacement in the show method that replaces the links to the +next and previous chapter so that it works with our routing configuration. + +`src/Action/Page.php` +```php +getPages(), + fn (string $filename) => str_contains($filename, $page) + ) + )[0]; + $markdown = file_get_contents($page); + + // fix the next and previous buttons to work with our routing + $markdown = preg_replace('/\(\d\d-/m', '(', $markdown); + $markdown = str_replace('.md)', ')', $markdown); + + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + $data = [ + 'title' => substr($page, 3), + 'content' => $this->parser->parse($markdown), + ]; + $html = $this->renderer->render('page/show', $data); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map(function (string $page) { + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + return [ + 'id' => substr($page, 0, 2), + 'title' => substr($page, 3), + ]; + }, $this->getPages()); + $html = $this->renderer->render('page/list', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } + + /** + * @return string[] + */ + private function getPages(): array + { + $files = glob($this->pagesPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return $files; + } +} +``` + +You can now navigate your Browser to [localhost:1234/page][http://localhost:1234/page] and try out if everything works. + +Of course this code is far from looking good. We heavily rely on the pages being files in the filesystem, and the action +should never be aware of the filesystem in the first place, also we have a lot of string replacements and other repetetive +code in the file. And phpstan is gonna scream at us a lot, but if we rewrite the code to satisfy all the checks we would +add even more lines to that simple class, so lets move on to the next chapter where we move all the logic to seperate +classes following our holy SOLID principles :) + + +[<< previous](14-middleware.md) | [next >>](14-invoker.md) diff --git a/app/composer.json b/app/composer.json index 04edde0..4809539 100644 --- a/app/composer.json +++ b/app/composer.json @@ -13,7 +13,8 @@ "middlewares/whoops": "^2.0", "erusev/parsedown": "^1.7", "symfony/cache": "^6.0", - "doctrine/orm": "^2.11" + "doctrine/orm": "^2.11", + "league/commonmark": "^2.2" }, "autoload": { "psr-4": { diff --git a/app/composer.lock b/app/composer.lock index 904cfb4..648f2d5 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -4,8 +4,83 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a3762dd11bab0c9e948d3a73b7f252b9", + "content-hash": "1e8469bfebe6479a139b946b8aba49de", "packages": [ + { + "name": "dflydev/dot-access-data", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^3.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" + }, + "time": "2021-08-13T13:06:58+00:00" + }, { "name": "doctrine/cache", "version": "2.1.1", @@ -1155,6 +1230,192 @@ ], "time": "2022-03-29T20:12:16+00:00" }, + { + "name": "league/commonmark", + "version": "2.2.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", + "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.15" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.30.0", + "commonmark/commonmark.js": "0.30.0", + "composer/package-versions-deprecated": "^1.8", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4", + "phpstan/phpstan": "^0.12.88 || ^1.0.0", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2022-02-26T21:24:45+00:00" + }, + { + "name": "league/config", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.90", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2021-08-14T12:15:32+00:00" + }, { "name": "middlewares/trailing-slash", "version": "v2.0.1", @@ -1378,6 +1639,153 @@ }, "time": "2022-01-21T06:08:36+00:00" }, + { + "name": "nette/schema", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", + "shasum": "" + }, + "require": { + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": ">=7.1 <8.2" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "phpstan/phpstan-nette": "^0.12", + "tracy/tracy": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.2.2" + }, + "time": "2021-10-15T11:40:02+00:00" + }, + { + "name": "nette/utils", + "version": "v3.2.7", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", + "shasum": "" + }, + "require": { + "php": ">=7.2 <8.2" + }, + "conflict": { + "nette/di": "<3.0.6" + }, + "require-dev": { + "nette/tester": "~2.0", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v3.2.7" + }, + "time": "2022-01-24T11:29:14+00:00" + }, { "name": "nikic/fast-route", "version": "v1.3.0", @@ -1763,6 +2171,56 @@ }, "time": "2021-11-05T16:50:12+00:00" }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, { "name": "psr/http-factory", "version": "1.0.1", diff --git a/app/config/dependencies.php b/app/config/dependencies.php index 4a97f31..e2a3925 100644 --- a/app/config/dependencies.php +++ b/app/config/dependencies.php @@ -14,13 +14,14 @@ use Lubian\NoFramework\Http\Pipeline; use Lubian\NoFramework\Http\RoutedRequestHandler; use Lubian\NoFramework\Http\RouteMiddleware; use Lubian\NoFramework\Repository\CachedMarkdownPageRepo; -use Lubian\NoFramework\Repository\DoctrineMarkdownPageRepo; use Lubian\NoFramework\Repository\MarkdownPageFilesystem; use Lubian\NoFramework\Repository\MarkdownPageRepo; use Lubian\NoFramework\Service\Time\Now; use Lubian\NoFramework\Service\Time\SystemClockNow; use Lubian\NoFramework\Settings; +use Lubian\NoFramework\Template\MarkdownParser; use Lubian\NoFramework\Template\MustacheRenderer; +use Lubian\NoFramework\Template\ParsedownParser; use Lubian\NoFramework\Template\Renderer; use Mustache_Engine as ME; use Mustache_Loader_FilesystemLoader as MLF; @@ -43,6 +44,7 @@ return [ RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, CacheInterface::class => fn (FilesystemAdapter $a) => $a, MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, + MarkdownParser::class => fn (ParsedownParser $p) => $p, // Factories ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), diff --git a/app/config/middlewares.php b/app/config/middlewares.php index 459547e..71dd461 100644 --- a/app/config/middlewares.php +++ b/app/config/middlewares.php @@ -1,13 +1,11 @@ getBody(); - - $body->write('This works too!'); - - return $response - ->withStatus(200) - ->withBody($body); + $html = $parser->parse('This *works* **too!**'); + $response->getBody()->write($html); + return $response->withStatus(200); } } diff --git a/app/src/Action/Page.php b/app/src/Action/Page.php index 9fb86c2..6a3aad0 100644 --- a/app/src/Action/Page.php +++ b/app/src/Action/Page.php @@ -2,50 +2,79 @@ namespace Lubian\NoFramework\Action; -use Lubian\NoFramework\Model\MarkdownPage; -use Lubian\NoFramework\Repository\MarkdownPageFilesystem; -use Lubian\NoFramework\Repository\MarkdownPageRepo; +use Lubian\NoFramework\Exception\InternalServerError; +use Lubian\NoFramework\Template\MarkdownParser; use Lubian\NoFramework\Template\Renderer; -use Parsedown; use Psr\Http\Message\ResponseInterface; +use function array_filter; +use function array_map; +use function array_values; +use function file_get_contents; +use function glob; use function preg_replace; +use function str_contains; use function str_replace; +use function substr; class Page { public function __construct( private ResponseInterface $response, - private MarkdownPageRepo $repo, - private Parsedown $parsedown, + private MarkdownParser $parser, private Renderer $renderer, - ){} + private string $pagesPath = __DIR__ . '/../../data/pages/' + ) { + } + public function show( string $page, ): ResponseInterface { - $page = $this->repo->byTitle($page); - $content = $this->linkFilter($page->content); - $content = $this->parsedown->parse($content); - $html = $this->renderer->render('page', ['content' => $content, 'title' => $page->title]); + $page = array_values( + array_filter( + $this->getPages(), + fn (string $filename) => str_contains($filename, $page) + ) + )[0]; + $markdown = file_get_contents($page); + + // fix the next and previous buttons to work with our routing + $markdown = preg_replace('/\(\d\d-/m', '(', $markdown); + $markdown = str_replace('.md)', ')', $markdown); + + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + $data = [ + 'title' => substr($page, 3), + 'content' => $this->parser->parse($markdown), + ]; + $html = $this->renderer->render('page/show', $data); $this->response->getBody()->write($html); return $this->response; } public function list(): ResponseInterface { - $pages = array_map( - fn (MarkdownPage $p) => ['title' => $p->title, 'id' => $p->id], - $this->repo->all() - ); - $html = $this->renderer->render('pagelist', ['pages' => $pages]); + $pages = array_map(function (string $page) { + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + return [ + 'id' => substr($page, 0, 2), + 'title' => substr($page, 3), + ]; + }, $this->getPages()); + $html = $this->renderer->render('page/list', ['pages' => $pages]); $this->response->getBody()->write($html); return $this->response; - } - private function linkFilter(string $content): string + /** + * @return string[] + */ + private function getPages(): array { - $content = preg_replace('/\(\d\d-/m', '(', $content); - return str_replace('.md)', ')', $content); + $files = glob($this->pagesPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return $files; } } diff --git a/app/src/Factory/DoctrineEm.php b/app/src/Factory/DoctrineEm.php index be24e7f..b0be39b 100644 --- a/app/src/Factory/DoctrineEm.php +++ b/app/src/Factory/DoctrineEm.php @@ -1,7 +1,4 @@ -settings->doctrine['devMode']); @@ -29,4 +29,4 @@ final class DoctrineEm $config, ); } -} \ No newline at end of file +} diff --git a/app/src/Factory/SettingsContainerProvider.php b/app/src/Factory/SettingsContainerProvider.php index baf278b..5b89266 100644 --- a/app/src/Factory/SettingsContainerProvider.php +++ b/app/src/Factory/SettingsContainerProvider.php @@ -20,7 +20,7 @@ final class SettingsContainerProvider implements ContainerProvider $dependencies = require $settings->dependenciesFile; $dependencies[Settings::class] = $settings; $builder->addDefinitions($dependencies); - // $builder->enableCompilation('/tmp'); + // $builder->enableCompilation('/tmp'); return $builder->build(); } } diff --git a/app/src/Middleware/CacheMiddleware.php b/app/src/Middleware/CacheMiddleware.php index 2ff6f6e..1bd58c5 100644 --- a/app/src/Middleware/CacheMiddleware.php +++ b/app/src/Middleware/CacheMiddleware.php @@ -19,13 +19,12 @@ final class CacheMiddleware implements MiddlewareInterface private CacheInterface $cache, private Response\Serializer $serializer, private Settings $settings, - ) - { + ) { } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { - if ($request->getMethod() === 'GET' && !$this->settings->isDev()) { + if ($request->getMethod() === 'GET' && ! $this->settings->isDev()) { $key = (string) $request->getUri(); $key = base64_encode($key); $callback = fn () => $handler->handle($request); diff --git a/app/src/Model/MarkdownPage.php b/app/src/Model/MarkdownPage.php index 73f001d..bae383c 100644 --- a/app/src/Model/MarkdownPage.php +++ b/app/src/Model/MarkdownPage.php @@ -3,17 +3,15 @@ namespace Lubian\NoFramework\Model; use Doctrine\DBAL\Types\Types; -use Doctrine\ORM\Mapping\Column; -use Doctrine\ORM\Mapping\Entity; -use Doctrine\ORM\Mapping\GeneratedValue; -use Doctrine\ORM\Mapping\Id; #[Entity] class MarkdownPage { public function __construct( - #[Id, Column, GeneratedValue] - public int|null $id = null, + #[Id, + Column, + GeneratedValue] + public int |null $id = null, #[Column] public string $title, #[Column(type: Types::TEXT)] diff --git a/app/src/Repository/DoctrineMarkdownPageRepo.php b/app/src/Repository/DoctrineMarkdownPageRepo.php index f107c9e..8d1d457 100644 --- a/app/src/Repository/DoctrineMarkdownPageRepo.php +++ b/app/src/Repository/DoctrineMarkdownPageRepo.php @@ -1,7 +1,4 @@ - - */ + /** @var EntityRepository */ private EntityRepository $repo; + public function __construct( private EntityManagerInterface $entityManager - ){ + ) { $this->repo = $this->entityManager->getRepository(MarkdownPage::class); } @@ -27,15 +26,15 @@ final class DoctrineMarkdownPageRepo implements MarkdownPageRepo */ public function all(): array { - usleep(rand(500, 1500) * 1000); + usleep(random_int(500, 1500) * 1000); return $this->repo->findAll(); } public function byId(int $id): MarkdownPage { - usleep(rand(500, 1500) * 1000); + usleep(random_int(500, 1500) * 1000); $page = $this->repo->findOneBy(['id' => $id]); - if (!$page instanceof MarkdownPage){ + if (! $page instanceof MarkdownPage) { throw new NotFound; } return $page; @@ -43,9 +42,9 @@ final class DoctrineMarkdownPageRepo implements MarkdownPageRepo public function byTitle(string $title): MarkdownPage { - usleep(rand(500, 1500) * 1000); + usleep(random_int(500, 1500) * 1000); $page = $this->repo->findOneBy(['title' => $title]); - if (!$page instanceof MarkdownPage){ + if (! $page instanceof MarkdownPage) { throw new NotFound; } return $page; @@ -57,4 +56,4 @@ final class DoctrineMarkdownPageRepo implements MarkdownPageRepo $this->entityManager->flush(); return $page; } -} \ No newline at end of file +} diff --git a/app/src/Repository/MarkdownPageFilesystem.php b/app/src/Repository/MarkdownPageFilesystem.php index 1d70998..25dfd97 100644 --- a/app/src/Repository/MarkdownPageFilesystem.php +++ b/app/src/Repository/MarkdownPageFilesystem.php @@ -13,6 +13,7 @@ use function count; use function file_get_contents; use function glob; use function is_array; +use function random_int; use function str_replace; use function substr; use function usleep; @@ -31,7 +32,7 @@ final class MarkdownPageFilesystem implements MarkdownPageRepo $fileNames = glob($this->dataPath . '*.md'); assert(is_array($fileNames)); return array_map(function (string $name): MarkdownPage { - usleep(rand(200, 500) * 1000); + usleep(random_int(200, 500) * 1000); $content = file_get_contents($name); $name = str_replace($this->dataPath, '', $name); $name = str_replace('.md', '', $name); diff --git a/app/src/Template/GithubMarkdownRenderer.php b/app/src/Template/GithubMarkdownRenderer.php new file mode 100644 index 0000000..121504b --- /dev/null +++ b/app/src/Template/GithubMarkdownRenderer.php @@ -0,0 +1,28 @@ +addExtension($commonMarkCoreExtension); + $environment->addExtension($githubFlavoredMarkdownExtension); + $this->engine = new MarkdownConverter($environment); + } + + public function render(string $markdown): string + { + return (string) $this->engine->convert($markdown); + } +} diff --git a/app/src/Template/MarkdownParser.php b/app/src/Template/MarkdownParser.php new file mode 100644 index 0000000..d404005 --- /dev/null +++ b/app/src/Template/MarkdownParser.php @@ -0,0 +1,8 @@ +parser->parse($markdown); + } +} diff --git a/app/templates/page/list.html b/app/templates/page/list.html new file mode 100644 index 0000000..bf42348 --- /dev/null +++ b/app/templates/page/list.html @@ -0,0 +1,19 @@ + + + + + Pages + + + +
+ +
+ + \ No newline at end of file diff --git a/app/templates/page/show.html b/app/templates/page/show.html new file mode 100644 index 0000000..ebe707a --- /dev/null +++ b/app/templates/page/show.html @@ -0,0 +1,13 @@ + + + + + {{title}} + + + +
+ {{{content}}} +
+ + \ No newline at end of file diff --git a/implementation/02-composer/composer.lock b/implementation/02-composer/composer.lock index 31ecc9c..6d84747 100644 --- a/implementation/02-composer/composer.lock +++ b/implementation/02-composer/composer.lock @@ -4,191 +4,9 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "83df32885d5f14b3e3e8dd6d88fafd23", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "phpstan/phpstan", - "version": "1.4.10", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.4.10" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-14T10:25:45+00:00" - } - ], + "content-hash": "bad16ec07065a74b5b41da54ff6ca57e", + "packages": [], + "packages-dev": [], "aliases": [], "minimum-stability": "stable", "stability-flags": [], From 9a1f78947b8b6b8e42f22b5881e19f66a24d7c16 Mon Sep 17 00:00:00 2001 From: lubiana Date: Wed, 6 Apr 2022 01:21:17 +0200 Subject: [PATCH 122/314] add chapter about data repositories, and start work on perfomance chapter --- 11-templating.md | 6 +- 12-configuration.md | 1 - 13-refactoring.md | 4 - 14-middleware.md | 9 +- 15-adding-content.md | 7 +- 16-data-repository.md | 265 + 17-performance.md | 20 + app/composer.json | 2 - app/composer.lock | 2006 +------- app/config/dependencies.php | 11 +- app/config/settings.php | 11 - app/data/pages/04-development-helpers.md | 4 +- app/data/pages/11-templating.md | 6 +- app/src/Action/Page.php | 54 +- app/src/Model/MarkdownPage.php | 8 - .../Repository/FileSystemMarkdownPageRepo.php | 61 + app/src/Repository/MarkdownPageRepo.php | 12 +- app/src/Settings.php | 17 - app/templates/page/show.html | 24 +- .../15-adding-content/.php-cs-fixer.php | 38 + .../15-adding-content/.phpcs.xml.dist | 9 + .../15-adding-content/cli-config.php | 13 + .../15-adding-content/composer.json | 56 + .../15-adding-content/composer.lock | 4246 +++++++++++++++++ .../15-adding-content/config/dependencies.php | 60 + .../15-adding-content/config/middlewares.php | 11 + .../15-adding-content/config/routes.php | 15 + .../15-adding-content/config/settings.php | 23 + .../data/pages/01-front-controller.md | 53 + .../data/pages/02-composer.md | 75 + .../data/pages/03-error-handler.md | 79 + .../data/pages/04-development-helpers.md | 260 + .../15-adding-content/data/pages/05-http.md | 124 + .../15-adding-content/data/pages/06-router.md | 101 + .../data/pages/07-dispatching-to-a-class.md | 137 + .../data/pages/08-inversion-of-control.md | 54 + .../data/pages/09-dependency-injector.md | 213 + .../data/pages/10-invoker.md | 102 + .../data/pages/11-templating.md | 240 + .../data/pages/12-configuration.md | 201 + .../data/pages/13-refactoring.md | 377 ++ .../data/pages/14-middleware.md | 298 ++ .../15-adding-content/phpstan-baseline.neon | 7 + implementation/15-adding-content/phpstan.neon | 8 + .../public/css/spectre-exp.min.css | 1 + .../public/css/spectre-icons.min.css | 1 + .../public/css/spectre.min.css | 1 + .../15-adding-content/public/favicon.ico | Bin 0 -> 15086 bytes .../15-adding-content/public/index.php | 5 + implementation/15-adding-content/src/.gitkeep | 0 .../15-adding-content/src/Action/Hello.php | 31 + .../15-adding-content/src/Action/Other.php | 16 + .../15-adding-content/src/Action/Page.php | 80 + .../15-adding-content/src/Bootstrap.php | 40 + .../src/Exception/InternalServerError.php | 9 + .../src/Exception/MethodNotAllowed.php | 9 + .../src/Exception/NotFound.php | 9 + .../src/Factory/ContainerProvider.php | 10 + .../src/Factory/DiactorosRequestFactory.php | 28 + .../src/Factory/DoctrineEm.php | 32 + .../Factory/FileSystemSettingsProvider.php | 22 + .../src/Factory/PipelineProvider.php | 25 + .../src/Factory/RequestFactory.php | 11 + .../src/Factory/SettingsContainerProvider.php | 26 + .../src/Factory/SettingsProvider.php | 10 + .../src/Http/BasicEmitter.php | 38 + .../src/Http/ContainerPipeline.php | 82 + .../15-adding-content/src/Http/Emitter.php | 10 + .../src/Http/InvokerRoutedHandler.php | 34 + .../15-adding-content/src/Http/Pipeline.php | 11 + .../src/Http/RouteMiddleware.php | 69 + .../src/Http/RoutedRequestHandler.php | 10 + .../15-adding-content/src/Kernel.php | 32 + .../src/Middleware/CacheMiddleware.php | 0 .../src/Model/MarkdownPage.php | 21 + .../src/Repository/CachedMarkdownPageRepo.php | 0 .../Repository/DoctrineMarkdownPageRepo.php | 0 .../src/Repository/MarkdownPageFilesystem.php | 0 .../src/Repository/MarkdownPageRepo.php | 19 + .../src/Service/Time/Now.php | 10 + .../src/Service/Time/SystemClockNow.php | 13 + .../15-adding-content/src/Settings.php | 33 + .../src/Template/GithubMarkdownRenderer.php | 0 .../src/Template/MarkdownParser.php | 8 + .../src/Template/MustacheRenderer.php | 17 + .../src/Template/ParsedownParser.php | 17 + .../src/Template/Renderer.php | 11 + .../15-adding-content/templates/hello.html | 6 + .../15-adding-content/templates/page.html | 5 + .../templates/page/list.html | 19 + .../templates/page/show.html | 17 + .../15-adding-content/templates/pagelist.html | 11 + .../templates/partials/foot.html | 3 + .../templates/partials/head.html | 11 + .../16-data-repository/.php-cs-fixer.php | 38 + .../16-data-repository/.phpcs.xml.dist | 9 + .../16-data-repository/cli-config.php | 13 + .../16-data-repository/composer.json | 54 + .../16-data-repository/composer.lock | 2438 ++++++++++ .../config/dependencies.php | 55 + .../16-data-repository/config/middlewares.php | 11 + .../16-data-repository/config/routes.php | 15 + .../16-data-repository/config/settings.php | 12 + .../data/pages/01-front-controller.md | 53 + .../data/pages/02-composer.md | 75 + .../data/pages/03-error-handler.md | 79 + .../data/pages/04-development-helpers.md | 260 + .../16-data-repository/data/pages/05-http.md | 124 + .../data/pages/06-router.md | 101 + .../data/pages/07-dispatching-to-a-class.md | 137 + .../data/pages/08-inversion-of-control.md | 54 + .../data/pages/09-dependency-injector.md | 213 + .../data/pages/10-invoker.md | 102 + .../data/pages/11-templating.md | 236 + .../data/pages/12-configuration.md | 201 + .../data/pages/13-refactoring.md | 377 ++ .../data/pages/14-middleware.md | 298 ++ .../16-data-repository/phpstan-baseline.neon | 7 + .../16-data-repository/phpstan.neon | 8 + .../public/css/spectre-exp.min.css | 1 + .../public/css/spectre-icons.min.css | 1 + .../public/css/spectre.min.css | 1 + .../16-data-repository/public/favicon.ico | Bin 0 -> 15086 bytes .../16-data-repository/public/index.php | 5 + .../16-data-repository/src/.gitkeep | 0 .../16-data-repository/src/Action/Hello.php | 31 + .../16-data-repository/src/Action/Other.php | 16 + .../16-data-repository/src/Action/Page.php | 60 + .../16-data-repository/src/Bootstrap.php | 40 + .../src/Exception/InternalServerError.php | 9 + .../src/Exception/MethodNotAllowed.php | 9 + .../src/Exception/NotFound.php | 9 + .../src/Factory/ContainerProvider.php | 10 + .../src/Factory/DiactorosRequestFactory.php | 28 + .../src/Factory/DoctrineEm.php | 32 + .../Factory/FileSystemSettingsProvider.php | 22 + .../src/Factory/PipelineProvider.php | 25 + .../src/Factory/RequestFactory.php | 11 + .../src/Factory/SettingsContainerProvider.php | 26 + .../src/Factory/SettingsProvider.php | 10 + .../src/Http/BasicEmitter.php | 38 + .../src/Http/ContainerPipeline.php | 82 + .../16-data-repository/src/Http/Emitter.php | 10 + .../src/Http/InvokerRoutedHandler.php | 34 + .../16-data-repository/src/Http/Pipeline.php | 11 + .../src/Http/RouteMiddleware.php | 69 + .../src/Http/RoutedRequestHandler.php | 10 + .../16-data-repository/src/Kernel.php | 32 + .../src/Model/MarkdownPage.php | 13 + .../Repository/FileSystemMarkdownPageRepo.php | 61 + .../src/Repository/MarkdownPageRepo.php | 15 + .../src/Service/Time/Now.php | 10 + .../src/Service/Time/SystemClockNow.php | 13 + .../16-data-repository/src/Settings.php | 16 + .../src/Template/MarkdownParser.php | 8 + .../src/Template/MustacheRenderer.php | 17 + .../src/Template/ParsedownParser.php | 17 + .../src/Template/Renderer.php | 11 + .../16-data-repository/templates/hello.html | 6 + .../16-data-repository/templates/page.html | 5 + .../templates/page/list.html | 19 + .../templates/page/show.html | 17 + .../templates/pagelist.html | 11 + .../templates/partials/foot.html | 3 + .../templates/partials/head.html | 11 + 165 files changed, 14028 insertions(+), 2028 deletions(-) create mode 100644 16-data-repository.md create mode 100644 17-performance.md create mode 100644 app/src/Repository/FileSystemMarkdownPageRepo.php create mode 100644 implementation/15-adding-content/.php-cs-fixer.php create mode 100644 implementation/15-adding-content/.phpcs.xml.dist create mode 100644 implementation/15-adding-content/cli-config.php create mode 100644 implementation/15-adding-content/composer.json create mode 100644 implementation/15-adding-content/composer.lock create mode 100644 implementation/15-adding-content/config/dependencies.php create mode 100644 implementation/15-adding-content/config/middlewares.php create mode 100644 implementation/15-adding-content/config/routes.php create mode 100644 implementation/15-adding-content/config/settings.php create mode 100644 implementation/15-adding-content/data/pages/01-front-controller.md create mode 100644 implementation/15-adding-content/data/pages/02-composer.md create mode 100644 implementation/15-adding-content/data/pages/03-error-handler.md create mode 100644 implementation/15-adding-content/data/pages/04-development-helpers.md create mode 100644 implementation/15-adding-content/data/pages/05-http.md create mode 100644 implementation/15-adding-content/data/pages/06-router.md create mode 100644 implementation/15-adding-content/data/pages/07-dispatching-to-a-class.md create mode 100644 implementation/15-adding-content/data/pages/08-inversion-of-control.md create mode 100644 implementation/15-adding-content/data/pages/09-dependency-injector.md create mode 100644 implementation/15-adding-content/data/pages/10-invoker.md create mode 100644 implementation/15-adding-content/data/pages/11-templating.md create mode 100644 implementation/15-adding-content/data/pages/12-configuration.md create mode 100644 implementation/15-adding-content/data/pages/13-refactoring.md create mode 100644 implementation/15-adding-content/data/pages/14-middleware.md create mode 100644 implementation/15-adding-content/phpstan-baseline.neon create mode 100644 implementation/15-adding-content/phpstan.neon create mode 100644 implementation/15-adding-content/public/css/spectre-exp.min.css create mode 100644 implementation/15-adding-content/public/css/spectre-icons.min.css create mode 100644 implementation/15-adding-content/public/css/spectre.min.css create mode 100644 implementation/15-adding-content/public/favicon.ico create mode 100644 implementation/15-adding-content/public/index.php create mode 100644 implementation/15-adding-content/src/.gitkeep create mode 100644 implementation/15-adding-content/src/Action/Hello.php create mode 100644 implementation/15-adding-content/src/Action/Other.php create mode 100644 implementation/15-adding-content/src/Action/Page.php create mode 100644 implementation/15-adding-content/src/Bootstrap.php create mode 100644 implementation/15-adding-content/src/Exception/InternalServerError.php create mode 100644 implementation/15-adding-content/src/Exception/MethodNotAllowed.php create mode 100644 implementation/15-adding-content/src/Exception/NotFound.php create mode 100644 implementation/15-adding-content/src/Factory/ContainerProvider.php create mode 100644 implementation/15-adding-content/src/Factory/DiactorosRequestFactory.php create mode 100644 implementation/15-adding-content/src/Factory/DoctrineEm.php create mode 100644 implementation/15-adding-content/src/Factory/FileSystemSettingsProvider.php create mode 100644 implementation/15-adding-content/src/Factory/PipelineProvider.php create mode 100644 implementation/15-adding-content/src/Factory/RequestFactory.php create mode 100644 implementation/15-adding-content/src/Factory/SettingsContainerProvider.php create mode 100644 implementation/15-adding-content/src/Factory/SettingsProvider.php create mode 100644 implementation/15-adding-content/src/Http/BasicEmitter.php create mode 100644 implementation/15-adding-content/src/Http/ContainerPipeline.php create mode 100644 implementation/15-adding-content/src/Http/Emitter.php create mode 100644 implementation/15-adding-content/src/Http/InvokerRoutedHandler.php create mode 100644 implementation/15-adding-content/src/Http/Pipeline.php create mode 100644 implementation/15-adding-content/src/Http/RouteMiddleware.php create mode 100644 implementation/15-adding-content/src/Http/RoutedRequestHandler.php create mode 100644 implementation/15-adding-content/src/Kernel.php rename {app => implementation/15-adding-content}/src/Middleware/CacheMiddleware.php (100%) create mode 100644 implementation/15-adding-content/src/Model/MarkdownPage.php rename {app => implementation/15-adding-content}/src/Repository/CachedMarkdownPageRepo.php (100%) rename {app => implementation/15-adding-content}/src/Repository/DoctrineMarkdownPageRepo.php (100%) rename {app => implementation/15-adding-content}/src/Repository/MarkdownPageFilesystem.php (100%) create mode 100644 implementation/15-adding-content/src/Repository/MarkdownPageRepo.php create mode 100644 implementation/15-adding-content/src/Service/Time/Now.php create mode 100644 implementation/15-adding-content/src/Service/Time/SystemClockNow.php create mode 100644 implementation/15-adding-content/src/Settings.php rename {app => implementation/15-adding-content}/src/Template/GithubMarkdownRenderer.php (100%) create mode 100644 implementation/15-adding-content/src/Template/MarkdownParser.php create mode 100644 implementation/15-adding-content/src/Template/MustacheRenderer.php create mode 100644 implementation/15-adding-content/src/Template/ParsedownParser.php create mode 100644 implementation/15-adding-content/src/Template/Renderer.php create mode 100644 implementation/15-adding-content/templates/hello.html create mode 100644 implementation/15-adding-content/templates/page.html create mode 100644 implementation/15-adding-content/templates/page/list.html create mode 100644 implementation/15-adding-content/templates/page/show.html create mode 100644 implementation/15-adding-content/templates/pagelist.html create mode 100644 implementation/15-adding-content/templates/partials/foot.html create mode 100644 implementation/15-adding-content/templates/partials/head.html create mode 100644 implementation/16-data-repository/.php-cs-fixer.php create mode 100644 implementation/16-data-repository/.phpcs.xml.dist create mode 100644 implementation/16-data-repository/cli-config.php create mode 100644 implementation/16-data-repository/composer.json create mode 100644 implementation/16-data-repository/composer.lock create mode 100644 implementation/16-data-repository/config/dependencies.php create mode 100644 implementation/16-data-repository/config/middlewares.php create mode 100644 implementation/16-data-repository/config/routes.php create mode 100644 implementation/16-data-repository/config/settings.php create mode 100644 implementation/16-data-repository/data/pages/01-front-controller.md create mode 100644 implementation/16-data-repository/data/pages/02-composer.md create mode 100644 implementation/16-data-repository/data/pages/03-error-handler.md create mode 100644 implementation/16-data-repository/data/pages/04-development-helpers.md create mode 100644 implementation/16-data-repository/data/pages/05-http.md create mode 100644 implementation/16-data-repository/data/pages/06-router.md create mode 100644 implementation/16-data-repository/data/pages/07-dispatching-to-a-class.md create mode 100644 implementation/16-data-repository/data/pages/08-inversion-of-control.md create mode 100644 implementation/16-data-repository/data/pages/09-dependency-injector.md create mode 100644 implementation/16-data-repository/data/pages/10-invoker.md create mode 100644 implementation/16-data-repository/data/pages/11-templating.md create mode 100644 implementation/16-data-repository/data/pages/12-configuration.md create mode 100644 implementation/16-data-repository/data/pages/13-refactoring.md create mode 100644 implementation/16-data-repository/data/pages/14-middleware.md create mode 100644 implementation/16-data-repository/phpstan-baseline.neon create mode 100644 implementation/16-data-repository/phpstan.neon create mode 100644 implementation/16-data-repository/public/css/spectre-exp.min.css create mode 100644 implementation/16-data-repository/public/css/spectre-icons.min.css create mode 100644 implementation/16-data-repository/public/css/spectre.min.css create mode 100644 implementation/16-data-repository/public/favicon.ico create mode 100644 implementation/16-data-repository/public/index.php create mode 100644 implementation/16-data-repository/src/.gitkeep create mode 100644 implementation/16-data-repository/src/Action/Hello.php create mode 100644 implementation/16-data-repository/src/Action/Other.php create mode 100644 implementation/16-data-repository/src/Action/Page.php create mode 100644 implementation/16-data-repository/src/Bootstrap.php create mode 100644 implementation/16-data-repository/src/Exception/InternalServerError.php create mode 100644 implementation/16-data-repository/src/Exception/MethodNotAllowed.php create mode 100644 implementation/16-data-repository/src/Exception/NotFound.php create mode 100644 implementation/16-data-repository/src/Factory/ContainerProvider.php create mode 100644 implementation/16-data-repository/src/Factory/DiactorosRequestFactory.php create mode 100644 implementation/16-data-repository/src/Factory/DoctrineEm.php create mode 100644 implementation/16-data-repository/src/Factory/FileSystemSettingsProvider.php create mode 100644 implementation/16-data-repository/src/Factory/PipelineProvider.php create mode 100644 implementation/16-data-repository/src/Factory/RequestFactory.php create mode 100644 implementation/16-data-repository/src/Factory/SettingsContainerProvider.php create mode 100644 implementation/16-data-repository/src/Factory/SettingsProvider.php create mode 100644 implementation/16-data-repository/src/Http/BasicEmitter.php create mode 100644 implementation/16-data-repository/src/Http/ContainerPipeline.php create mode 100644 implementation/16-data-repository/src/Http/Emitter.php create mode 100644 implementation/16-data-repository/src/Http/InvokerRoutedHandler.php create mode 100644 implementation/16-data-repository/src/Http/Pipeline.php create mode 100644 implementation/16-data-repository/src/Http/RouteMiddleware.php create mode 100644 implementation/16-data-repository/src/Http/RoutedRequestHandler.php create mode 100644 implementation/16-data-repository/src/Kernel.php create mode 100644 implementation/16-data-repository/src/Model/MarkdownPage.php create mode 100644 implementation/16-data-repository/src/Repository/FileSystemMarkdownPageRepo.php create mode 100644 implementation/16-data-repository/src/Repository/MarkdownPageRepo.php create mode 100644 implementation/16-data-repository/src/Service/Time/Now.php create mode 100644 implementation/16-data-repository/src/Service/Time/SystemClockNow.php create mode 100644 implementation/16-data-repository/src/Settings.php create mode 100644 implementation/16-data-repository/src/Template/MarkdownParser.php create mode 100644 implementation/16-data-repository/src/Template/MustacheRenderer.php create mode 100644 implementation/16-data-repository/src/Template/ParsedownParser.php create mode 100644 implementation/16-data-repository/src/Template/Renderer.php create mode 100644 implementation/16-data-repository/templates/hello.html create mode 100644 implementation/16-data-repository/templates/page.html create mode 100644 implementation/16-data-repository/templates/page/list.html create mode 100644 implementation/16-data-repository/templates/page/show.html create mode 100644 implementation/16-data-repository/templates/pagelist.html create mode 100644 implementation/16-data-repository/templates/partials/foot.html create mode 100644 implementation/16-data-repository/templates/partials/head.html diff --git a/11-templating.md b/11-templating.md index 3759664..7bfe1aa 100644 --- a/11-templating.md +++ b/11-templating.md @@ -49,11 +49,7 @@ namespace Lubian\NoFramework\Template; interface Renderer { - /** - * @param string $template - * @param array $data - * @return string - */ + /** @param array $data */ public function render(string $template, array $data = []) : string; } ``` diff --git a/12-configuration.md b/12-configuration.md index a44dfd5..4b60c19 100644 --- a/12-configuration.md +++ b/12-configuration.md @@ -172,7 +172,6 @@ return [ ``` Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: -require __DIR__ . '/../vendor/autoload.php'; ```php ... diff --git a/13-refactoring.md b/13-refactoring.md index 067e168..6dbbb8d 100644 --- a/13-refactoring.md +++ b/13-refactoring.md @@ -97,10 +97,6 @@ use Psr\Http\Server\RequestHandlerInterface; interface RoutedRequestHandler extends RequestHandlerInterface { - /** - * sets the Name of the ServerRequest attribute where the route - * information should be stored. - */ public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void; } ``` diff --git a/14-middleware.md b/14-middleware.md index 087e26b..81f82a5 100644 --- a/14-middleware.md +++ b/14-middleware.md @@ -153,8 +153,6 @@ class ContainerPipeline implements Pipeline { /** * @param array $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container */ public function __construct( private array $middlewares, @@ -295,4 +293,11 @@ Lets try if you can make the kernel work with our created Pipeline implementatio pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a dependency container, if you want you can do that as well. +**A quick note about docblocks:** You might have noticed, that I rarely add docblocks to my the code in the examples, and +when I do it seems kind of random. My philosophy is that I only add docblocks when there is no way to automatically get +the exact type from the code itself. For me docblocks only serve two purposes: help my IDE to understand what it choices +it has for code completion and to help the static analysis to better understand the code. There is a great blogpost +about the [cost and value of DocBlocks](https://localheinz.com/blog/2018/05/06/cost-and-value-of-docblocks/), although it +is written in 2018 at a time before PHP 7.4 was around everything written there is still valid today. + [<< previous](12-refactoring.md) | [next >>](15-adding-content.md) diff --git a/15-adding-content.md b/15-adding-content.md index d1a7348..c894d66 100644 --- a/15-adding-content.md +++ b/15-adding-content.md @@ -114,6 +114,10 @@ those are simply displayed using an unordered list. {{title}} + + +
@@ -129,7 +133,8 @@ html and thereby destroys the the parsed markdown. You might have spotted that I added [Pico.css](https://picocss.com/) which is just a very small css framework to make the pages a little bit nicer to look at. It mostly provides some typography styles that work great with rendered Markdown, -but you can leave that out or use any other css framework you like. +but you can leave that out or use any other css framework you like. There is also some Javascript that adds syntax +highlighting to the code. After you have taken care of the templating side we can now create an new Action class with two methods to display use our markdown files and the templates to create the pages. As we have two templates I propose to use Two methods in our diff --git a/16-data-repository.md b/16-data-repository.md new file mode 100644 index 0000000..d9a3218 --- /dev/null +++ b/16-data-repository.md @@ -0,0 +1,265 @@ +[<< previous](15-adding-content.md) | [next >>](17-performance.md) + +## Data Repository + +At the end of the last chapter I mentioned being unhappy with our Pages action, because there is to much stuff happening +there. We are firstly receiving some Arguments, then we are using those to query the filesytem for the given page, +loading the specific file from the filesystem, rendering the markdown, passing the markdown to the template renderer, +adding the resulting html to the response and then returning the response. + +In order to make our pageaction independent from the filesystem and move the code that is responsible for reading the +files +to a better place I want to introduce +the [Repository Pattern](https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html). + +I want to start by creating a class that represents the Data that is included in a page so that. For now I can spot +three +distrinct attributes. + +* the ID (or chapternumber) +* the title (or name) +* the content + +Currently all those properties are always available, but we might later be able to create new pages and store them, but +at that point in time we are not yet aware of the new available ID, so we should leave that property nullable. This +allows +us to create an object without an id and let the code that actually saves the object to a persistant store define a +valid +id on saving. + +Lets create an new Namespace called `Model` and put a `MarkdownPage.php` class in there: + +```php +dataPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return array_map(function (string $filename) { + $content = file_get_contents($filename); + if ($content === false) { + throw new InternalServerError('cannot read pages'); + } + $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); + return new MarkdownPage( + (int) substr($idAndTitle, 0, 2), + substr($idAndTitle, 3), + $content + ); + }, $files); + } + + public function byName(string $name): MarkdownPage + { + $pages = array_values( + array_filter( + $this->all(), + fn (MarkdownPage $p) => $p->title === $name, + ) + ); + + if (count($pages) !== 1) { + throw new NotFound; + } + + return $pages[0]; + } +} +``` + +With that in place we need to add the required `$pagesPath` to our settings class and add specify that in our +configuration. + +`src/Settings.php` + +```php +final class Settings +{ + public function __construct( + public readonly string $environment, + public readonly string $dependenciesFile, + public readonly string $middlewaresFile, + public readonly string $templateDir, + public readonly string $templateExtension, + public readonly string $pagesPath, + ) { + } +} +``` + +`config/settings.php` + +```php +return new Settings( + environment: 'prod', + dependenciesFile: __DIR__ . '/dependencies.php', + middlewaresFile: __DIR__ . '/middlewares.php', + templateDir: __DIR__ . '/../templates', + templateExtension: '.html', + pagesPath: __DIR__ . '/../data/pages/', +); +``` + +Of course we need to define the correct implementation for the container to choose when we are requesting the Repository +interface: +`conf/dependencies.php` + +```php +MarkdownPageRepo::class => fn (FileSystemMarkdownPageRepo $r) => $r, +FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), +``` + +Now you can request the MarkdownPageRepo Interface in your page action and use the defined functions to get the +MarkdownPage +Objects. My `src/Action/Page.php` looks like this now: + +```php +repo->byName($page); + + // fix the next and previous buttons to work with our routing + $content = preg_replace('/\(\d\d-/m', '(', $page->content); + assert(is_string($content)); + $content = str_replace('.md)', ')', $content); + + $data = [ + 'title' => $page->title, + 'content' => $this->parser->parse($content), + ]; + + $html = $this->renderer->render('page/show', $data); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map(function (MarkdownPage $page) { + return [ + 'id' => $page->id, + 'title' => $page->content, + ]; + }, $this->repo->all()); + + $html = $this->renderer->render('page/list', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } +} +``` + +Check the page in your browser if everything still works, don't forget to run phpstan and the others fixers before +committing your changes and moving on to the next chapter. + +[<< previous](15-adding-content.md) | [next >>](17-performance.md) diff --git a/17-performance.md b/17-performance.md new file mode 100644 index 0000000..d457bff --- /dev/null +++ b/17-performance.md @@ -0,0 +1,20 @@ +[<< previous](15-adding-content.md) | [next >>](17-performance.md) + +## Performance + +Although our application is still very small and you should not really experience any performance issues right now, +there are still some things we can already consider and take a look at. If I check the network tab in my browser it takes +about 90-400ms to show a simple rendered markdownpage, with is sort of ok but in my opinion way to long as we are not +really doing anything and do not connect to any external services. Mostly we are just reading around 16 markdown files, +a template, some config files here and there and parse some markdown. So that should not really take that long. + +The problem is, that we heavily rely on autoloading for all our class files, in the `src` folder. And there are also +quite a lot of other files in composers `vendor` directory. To understand while this is becomming we should make +ourselves familiar with how autoloading in PHP works. + +[autoloading in php](https://www.php.net/manual/en/language.oop5.autoload.php) +[composer autoloader optimization](https://getcomposer.org/doc/articles/autoloader-optimization.md) + +### Composer autoloading + +[<< previous](15-adding-content.md) | [next >>](17-performance.md) diff --git a/app/composer.json b/app/composer.json index 4809539..b5c7f1a 100644 --- a/app/composer.json +++ b/app/composer.json @@ -12,8 +12,6 @@ "middlewares/trailing-slash": "^2.0", "middlewares/whoops": "^2.0", "erusev/parsedown": "^1.7", - "symfony/cache": "^6.0", - "doctrine/orm": "^2.11", "league/commonmark": "^2.2" }, "autoload": { diff --git a/app/composer.lock b/app/composer.lock index 648f2d5..a62d9c7 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1e8469bfebe6479a139b946b8aba49de", + "content-hash": "00acf07ae222f9117a84bce157b99837", "packages": [ { "name": "dflydev/dot-access-data", @@ -81,935 +81,6 @@ }, "time": "2021-08-13T13:06:58+00:00" }, - { - "name": "doctrine/cache", - "version": "2.1.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/cache.git", - "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/331b4d5dbaeab3827976273e9356b3b453c300ce", - "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce", - "shasum": "" - }, - "require": { - "php": "~7.1 || ^8.0" - }, - "conflict": { - "doctrine/common": ">2.2,<2.4" - }, - "require-dev": { - "alcaeus/mongo-php-adapter": "^1.1", - "cache/integration-tests": "dev-master", - "doctrine/coding-standard": "^8.0", - "mongodb/mongodb": "^1.1", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "predis/predis": "~1.0", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^4.4 || ^5.2 || ^6.0@dev", - "symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev" - }, - "suggest": { - "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", - "homepage": "https://www.doctrine-project.org/projects/cache.html", - "keywords": [ - "abstraction", - "apcu", - "cache", - "caching", - "couchdb", - "memcached", - "php", - "redis", - "xcache" - ], - "support": { - "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/2.1.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", - "type": "tidelift" - } - ], - "time": "2021-07-17T14:49:29+00:00" - }, - { - "name": "doctrine/collections", - "version": "1.6.8", - "source": { - "type": "git", - "url": "https://github.com/doctrine/collections.git", - "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/1958a744696c6bb3bb0d28db2611dc11610e78af", - "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af", - "shasum": "" - }, - "require": { - "php": "^7.1.3 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5", - "vimeo/psalm": "^4.2.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", - "homepage": "https://www.doctrine-project.org/projects/collections.html", - "keywords": [ - "array", - "collections", - "iterators", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/1.6.8" - }, - "time": "2021-08-10T18:51:53+00:00" - }, - { - "name": "doctrine/common", - "version": "3.2.2", - "source": { - "type": "git", - "url": "https://github.com/doctrine/common.git", - "reference": "295082d3750987065912816a9d536c2df735f637" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/295082d3750987065912816a9d536c2df735f637", - "reference": "295082d3750987065912816a9d536c2df735f637", - "shasum": "" - }, - "require": { - "doctrine/persistence": "^2.0", - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "^1.4.1", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", - "squizlabs/php_codesniffer": "^3.0", - "symfony/phpunit-bridge": "^4.0.5", - "vimeo/psalm": "^4.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", - "homepage": "https://www.doctrine-project.org/projects/common.html", - "keywords": [ - "common", - "doctrine", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/common/issues", - "source": "https://github.com/doctrine/common/tree/3.2.2" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", - "type": "tidelift" - } - ], - "time": "2022-02-02T09:15:57+00:00" - }, - { - "name": "doctrine/dbal", - "version": "3.3.4", - "source": { - "type": "git", - "url": "https://github.com/doctrine/dbal.git", - "reference": "83f779beaea1893c0bece093ab2104c6d15a7f26" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/83f779beaea1893c0bece093ab2104c6d15a7f26", - "reference": "83f779beaea1893c0bece093ab2104c6d15a7f26", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2", - "doctrine/cache": "^1.11|^2.0", - "doctrine/deprecations": "^0.5.3", - "doctrine/event-manager": "^1.0", - "php": "^7.3 || ^8.0", - "psr/cache": "^1|^2|^3", - "psr/log": "^1|^2|^3" - }, - "require-dev": { - "doctrine/coding-standard": "9.0.0", - "jetbrains/phpstorm-stubs": "2021.1", - "phpstan/phpstan": "1.4.6", - "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "9.5.16", - "psalm/plugin-phpunit": "0.16.1", - "squizlabs/php_codesniffer": "3.6.2", - "symfony/cache": "^5.2|^6.0", - "symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0", - "vimeo/psalm": "4.22.0" - }, - "suggest": { - "symfony/console": "For helpful console commands such as SQL execution and import of files." - }, - "bin": [ - "bin/doctrine-dbal" - ], - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\DBAL\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - } - ], - "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", - "homepage": "https://www.doctrine-project.org/projects/dbal.html", - "keywords": [ - "abstraction", - "database", - "db2", - "dbal", - "mariadb", - "mssql", - "mysql", - "oci8", - "oracle", - "pdo", - "pgsql", - "postgresql", - "queryobject", - "sasql", - "sql", - "sqlite", - "sqlserver", - "sqlsrv" - ], - "support": { - "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.3.4" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", - "type": "tidelift" - } - ], - "time": "2022-03-20T18:37:29+00:00" - }, - { - "name": "doctrine/deprecations", - "version": "v0.5.3", - "source": { - "type": "git", - "url": "https://github.com/doctrine/deprecations.git", - "reference": "9504165960a1f83cc1480e2be1dd0a0478561314" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314", - "reference": "9504165960a1f83cc1480e2be1dd0a0478561314", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^6.0|^7.0|^8.0", - "phpunit/phpunit": "^7.0|^8.0|^9.0", - "psr/log": "^1.0" - }, - "suggest": { - "psr/log": "Allows logging deprecations via PSR-3 logger implementation" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", - "homepage": "https://www.doctrine-project.org/", - "support": { - "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v0.5.3" - }, - "time": "2021-03-21T12:59:47+00:00" - }, - { - "name": "doctrine/event-manager", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/event-manager.git", - "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", - "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/common": "<2.9@dev" - }, - "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpunit/phpunit": "^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/event-manager.html", - "keywords": [ - "event", - "event dispatcher", - "event manager", - "event system", - "events" - ], - "support": { - "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/1.1.x" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", - "type": "tidelift" - } - ], - "time": "2020-05-29T18:28:51+00:00" - }, - { - "name": "doctrine/inflector", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/doctrine/inflector.git", - "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", - "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^8.2", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "vimeo/psalm": "^4.10" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", - "homepage": "https://www.doctrine-project.org/projects/inflector.html", - "keywords": [ - "inflection", - "inflector", - "lowercase", - "manipulation", - "php", - "plural", - "singular", - "strings", - "uppercase", - "words" - ], - "support": { - "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.4" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", - "type": "tidelift" - } - ], - "time": "2021-10-22T20:16:43+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2022-03-03T08:28:38+00:00" - }, - { - "name": "doctrine/lexer", - "version": "1.2.3", - "source": { - "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.11" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", - "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.2.3" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" - } - ], - "time": "2022-02-28T11:07:21+00:00" - }, - { - "name": "doctrine/orm", - "version": "2.11.2", - "source": { - "type": "git", - "url": "https://github.com/doctrine/orm.git", - "reference": "9c351e044478135aec1755e2c0c0493a4b6309db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/9c351e044478135aec1755e2c0c0493a4b6309db", - "reference": "9c351e044478135aec1755e2c0c0493a4b6309db", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2", - "doctrine/cache": "^1.12.1 || ^2.1.1", - "doctrine/collections": "^1.5", - "doctrine/common": "^3.0.3", - "doctrine/dbal": "^2.13.1 || ^3.2", - "doctrine/deprecations": "^0.5.3", - "doctrine/event-manager": "^1.1", - "doctrine/inflector": "^1.4 || ^2.0", - "doctrine/instantiator": "^1.3", - "doctrine/lexer": "^1.0", - "doctrine/persistence": "^2.2", - "ext-ctype": "*", - "php": "^7.1 || ^8.0", - "psr/cache": "^1 || ^2 || ^3", - "symfony/console": "^3.0 || ^4.0 || ^5.0 || ^6.0", - "symfony/polyfill-php72": "^1.23", - "symfony/polyfill-php80": "^1.15" - }, - "conflict": { - "doctrine/annotations": "<1.13 || >= 2.0" - }, - "require-dev": { - "doctrine/annotations": "^1.13", - "doctrine/coding-standard": "^9.0", - "phpbench/phpbench": "^0.16.10 || ^1.0", - "phpstan/phpstan": "1.4.6", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", - "squizlabs/php_codesniffer": "3.6.2", - "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0", - "vimeo/psalm": "4.22.0" - }, - "suggest": { - "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0", - "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" - }, - "bin": [ - "bin/doctrine" - ], - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\ORM\\": "lib/Doctrine/ORM" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "Object-Relational-Mapper for PHP", - "homepage": "https://www.doctrine-project.org/projects/orm.html", - "keywords": [ - "database", - "orm" - ], - "support": { - "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/2.11.2" - }, - "time": "2022-03-09T15:23:58+00:00" - }, - { - "name": "doctrine/persistence", - "version": "2.4.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/persistence.git", - "reference": "092a52b71410ac1795287bb5135704ef07d18dd0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/092a52b71410ac1795287bb5135704ef07d18dd0", - "reference": "092a52b71410ac1795287bb5135704ef07d18dd0", - "shasum": "" - }, - "require": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/collections": "^1.0", - "doctrine/deprecations": "^0.5.3", - "doctrine/event-manager": "^1.0", - "php": "^7.1 || ^8.0", - "psr/cache": "^1.0 || ^2.0 || ^3.0" - }, - "conflict": { - "doctrine/annotations": "<1.0 || >=2.0", - "doctrine/common": "<2.10" - }, - "require-dev": { - "composer/package-versions-deprecated": "^1.11", - "doctrine/annotations": "^1.0", - "doctrine/coding-standard": "^9.0", - "doctrine/common": "^3.0", - "phpstan/phpstan": "1.4.6", - "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.5", - "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "vimeo/psalm": "4.21.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "src/Common", - "Doctrine\\Persistence\\": "src/Persistence" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", - "homepage": "https://doctrine-project.org/projects/persistence.html", - "keywords": [ - "mapper", - "object", - "odm", - "orm", - "persistence" - ], - "support": { - "issues": "https://github.com/doctrine/persistence/issues", - "source": "https://github.com/doctrine/persistence/tree/2.4.1" - }, - "time": "2022-03-22T06:44:40+00:00" - }, { "name": "erusev/parsedown", "version": "1.7.4", @@ -2074,55 +1145,6 @@ }, "time": "2020-10-12T12:39:22+00:00" }, - { - "name": "psr/cache", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" - }, - "time": "2021-02-03T23:26:27+00:00" - }, { "name": "psr/container", "version": "1.1.2", @@ -2494,118 +1516,21 @@ "time": "2021-07-14T16:46:02+00:00" }, { - "name": "symfony/cache", - "version": "v6.0.6", + "name": "symfony/deprecation-contracts", + "version": "v3.0.1", "source": { "type": "git", - "url": "https://github.com/symfony/cache.git", - "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", - "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", "shasum": "" }, "require": { - "php": ">=8.0.2", - "psr/cache": "^2.0|^3.0", - "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^1.1.7|^2|^3", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/var-exporter": "^5.4|^6.0" - }, - "conflict": { - "doctrine/dbal": "<2.13.1", - "symfony/dependency-injection": "<5.4", - "symfony/http-kernel": "<5.4", - "symfony/var-dumper": "<5.4" - }, - "provide": { - "psr/cache-implementation": "2.0|3.0", - "psr/simple-cache-implementation": "1.0|2.0|3.0", - "symfony/cache-implementation": "1.1|2.0|3.0" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/dbal": "^2.13.1|^3.0", - "predis/predis": "^1.1", - "psr/simple-cache": "^1.0|^2.0|^3.0", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/filesystem": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/messenger": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Cache\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", - "homepage": "https://symfony.com", - "keywords": [ - "caching", - "psr6" - ], - "support": { - "source": "https://github.com/symfony/cache/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "symfony/cache-contracts", - "version": "v3.0.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache-contracts.git", - "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/2f7463f156cf9c665d9317e21a809c3bbff5754e", - "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "psr/cache": "^3.0" - }, - "suggest": { - "symfony/cache-implementation": "" + "php": ">=8.0.2" }, "type": "library", "extra": { @@ -2617,176 +1542,6 @@ "url": "https://github.com/symfony/contracts" } }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Cache\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to caching", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.0.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-08-17T15:35:52+00:00" - }, - { - "name": "symfony/console", - "version": "v6.0.5", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "3bebf4108b9e07492a2a4057d207aa5a77d146b1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/3bebf4108b9e07492a2a4057d207aa5a77d146b1", - "reference": "3bebf4108b9e07492a2a4057d207aa5a77d146b1", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.4|^6.0" - }, - "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/lock": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command line", - "console", - "terminal" - ], - "support": { - "source": "https://github.com/symfony/console/tree/v6.0.5" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-02-25T10:48:52+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v2.5.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8", - "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, "autoload": { "files": [ "function.php" @@ -2809,7 +1564,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.1" }, "funding": [ { @@ -2825,413 +1580,7 @@ "type": "tidelift" } ], - "time": "2021-07-12T14:48:14+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "30885182c981ab175d4d034db0f6f469898070ab" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", - "reference": "30885182c981ab175d4d034db0f6f469898070ab", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-10-20T20:35:02+00:00" - }, - { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "81b86b50cf841a64252b439e738e97f4a34e2783" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", - "reference": "81b86b50cf841a64252b439e738e97f4a34e2783", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-23T21:10:46+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-02-19T12:13:01+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/polyfill-php72", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", - "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-05-27T09:17:38+00:00" + "time": "2022-01-02T09:55:41+00:00" }, { "name": "symfony/polyfill-php80", @@ -3315,246 +1664,6 @@ } ], "time": "2022-03-04T08:16:47+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v2.5.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", - "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "suggest": { - "symfony/service-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-04T16:48:04+00:00" - }, - { - "name": "symfony/string", - "version": "v6.0.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/522144f0c4c004c80d56fa47e40e17028e2eefc2", - "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/translation-contracts": "<2.0" - }, - "require-dev": { - "symfony/error-handler": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/translation-contracts": "^2.0|^3.0", - "symfony/var-exporter": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "files": [ - "Resources/functions.php" - ], - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "source": "https://github.com/symfony/string/tree/v6.0.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:55:41+00:00" - }, - { - "name": "symfony/var-exporter", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-exporter.git", - "reference": "130229a482abf17635a685590958894dfb4b4360" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/130229a482abf17635a685590958894dfb4b4360", - "reference": "130229a482abf17635a685590958894dfb4b4360", - "shasum": "" - }, - "require": { - "php": ">=8.0.2" - }, - "require-dev": { - "symfony/var-dumper": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\VarExporter\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows exporting any serializable PHP data structure to plain PHP code", - "homepage": "https://symfony.com", - "keywords": [ - "clone", - "construct", - "export", - "hydrate", - "instantiate", - "serialize" - ], - "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" } ], "packages-dev": [ @@ -3864,16 +1973,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.5.3", + "version": "1.5.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7" + "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/39953ac1452a8843702ee41a35b4861d3e8207a7", - "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", + "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", "shasum": "" }, "require": { @@ -3899,7 +2008,7 @@ "description": "PHPStan - PHP Static Analysis Tool", "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.3" + "source": "https://github.com/phpstan/phpstan/tree/1.5.4" }, "funding": [ { @@ -3919,7 +2028,7 @@ "type": "tidelift" } ], - "time": "2022-03-30T21:55:08+00:00" + "time": "2022-04-03T12:39:00+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -4089,6 +2198,89 @@ }, "time": "2021-12-12T21:44:58+00:00" }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, { "name": "symfony/var-dumper", "version": "v6.0.6", diff --git a/app/config/dependencies.php b/app/config/dependencies.php index e2a3925..0040933 100644 --- a/app/config/dependencies.php +++ b/app/config/dependencies.php @@ -1,10 +1,8 @@ fn (InvokerRoutedHandler $h) => $h, RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, CacheInterface::class => fn (FilesystemAdapter $a) => $a, - MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, MarkdownParser::class => fn (ParsedownParser $p) => $p, + MarkdownPageRepo::class => fn (FileSystemMarkdownPageRepo $r) => $r, // Factories ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), @@ -54,7 +51,5 @@ return [ ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - MarkdownPageFilesystem::class => fn (Settings $s) => new MarkdownPageFilesystem($s->pagesPath), - CachedMarkdownPageRepo::class => fn (CacheInterface $c, MarkdownPageFilesystem $r, Settings $s) => new CachedMarkdownPageRepo($c, $r, $s), - EntityManagerInterface::class => fn (DoctrineEm $f) => $f->create(), + FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), ]; diff --git a/app/config/settings.php b/app/config/settings.php index 5c58216..c654565 100644 --- a/app/config/settings.php +++ b/app/config/settings.php @@ -9,15 +9,4 @@ return new Settings( templateDir: __DIR__ . '/../templates', templateExtension: '.html', pagesPath: __DIR__ . '/../data/pages/', - connection: [ - 'driver' => 'pdo_sqlite', - 'user' => '', - 'password' => '', - 'path' => __DIR__ . '/../data/db.sqlite', - ], - doctrine: [ - 'devMode' => true, - 'metadataDirs' => [__DIR__ . '/../src/Model/'], - 'cacheDir' => __DIR__ . '/../data/cache/', - ], ); diff --git a/app/data/pages/04-development-helpers.md b/app/data/pages/04-development-helpers.md index 74f913c..9505284 100644 --- a/app/data/pages/04-development-helpers.md +++ b/app/data/pages/04-development-helpers.md @@ -174,7 +174,7 @@ return $config The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. -it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. +it provides the `phpcs` command to check for violations and the `phpcbf` command to actually fix most of the violations. Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have guessed we are adding some extra rules. @@ -216,7 +216,7 @@ PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY Time: 639ms; Memory: 10MB ``` -You can then use `./vendor/bin/phpcbf` to try to fix them +You can then use `./vendor/bin/phpcbf` to try to fix them. #### Symfony Var-Dumper diff --git a/app/data/pages/11-templating.md b/app/data/pages/11-templating.md index 3759664..7bfe1aa 100644 --- a/app/data/pages/11-templating.md +++ b/app/data/pages/11-templating.md @@ -49,11 +49,7 @@ namespace Lubian\NoFramework\Template; interface Renderer { - /** - * @param string $template - * @param array $data - * @return string - */ + /** @param array $data */ public function render(string $template, array $data = []) : string; } ``` diff --git a/app/src/Action/Page.php b/app/src/Action/Page.php index 6a3aad0..4af45f0 100644 --- a/app/src/Action/Page.php +++ b/app/src/Action/Page.php @@ -2,20 +2,17 @@ namespace Lubian\NoFramework\Action; -use Lubian\NoFramework\Exception\InternalServerError; +use Lubian\NoFramework\Model\MarkdownPage; +use Lubian\NoFramework\Repository\MarkdownPageRepo; use Lubian\NoFramework\Template\MarkdownParser; use Lubian\NoFramework\Template\Renderer; use Psr\Http\Message\ResponseInterface; -use function array_filter; use function array_map; -use function array_values; -use function file_get_contents; -use function glob; +use function assert; +use function is_string; use function preg_replace; -use function str_contains; use function str_replace; -use function substr; class Page { @@ -23,30 +20,25 @@ class Page private ResponseInterface $response, private MarkdownParser $parser, private Renderer $renderer, - private string $pagesPath = __DIR__ . '/../../data/pages/' + private MarkdownPageRepo $repo, ) { } public function show( string $page, ): ResponseInterface { - $page = array_values( - array_filter( - $this->getPages(), - fn (string $filename) => str_contains($filename, $page) - ) - )[0]; - $markdown = file_get_contents($page); + $page = $this->repo->byName($page); // fix the next and previous buttons to work with our routing - $markdown = preg_replace('/\(\d\d-/m', '(', $markdown); - $markdown = str_replace('.md)', ')', $markdown); + $content = preg_replace('/\(\d\d-/m', '(', $page->content); + assert(is_string($content)); + $content = str_replace('.md)', ')', $content); - $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); $data = [ - 'title' => substr($page, 3), - 'content' => $this->parser->parse($markdown), + 'title' => $page->title, + 'content' => $this->parser->parse($content), ]; + $html = $this->renderer->render('page/show', $data); $this->response->getBody()->write($html); return $this->response; @@ -54,27 +46,15 @@ class Page public function list(): ResponseInterface { - $pages = array_map(function (string $page) { - $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + $pages = array_map(function (MarkdownPage $page) { return [ - 'id' => substr($page, 0, 2), - 'title' => substr($page, 3), + 'id' => $page->id, + 'title' => $page->content, ]; - }, $this->getPages()); + }, $this->repo->all()); + $html = $this->renderer->render('page/list', ['pages' => $pages]); $this->response->getBody()->write($html); return $this->response; } - - /** - * @return string[] - */ - private function getPages(): array - { - $files = glob($this->pagesPath . '*.md'); - if ($files === false) { - throw new InternalServerError('cannot read pages'); - } - return $files; - } } diff --git a/app/src/Model/MarkdownPage.php b/app/src/Model/MarkdownPage.php index bae383c..df244fd 100644 --- a/app/src/Model/MarkdownPage.php +++ b/app/src/Model/MarkdownPage.php @@ -2,19 +2,11 @@ namespace Lubian\NoFramework\Model; -use Doctrine\DBAL\Types\Types; - -#[Entity] class MarkdownPage { public function __construct( - #[Id, - Column, - GeneratedValue] public int |null $id = null, - #[Column] public string $title, - #[Column(type: Types::TEXT)] public string $content, ) { } diff --git a/app/src/Repository/FileSystemMarkdownPageRepo.php b/app/src/Repository/FileSystemMarkdownPageRepo.php new file mode 100644 index 0000000..cca350e --- /dev/null +++ b/app/src/Repository/FileSystemMarkdownPageRepo.php @@ -0,0 +1,61 @@ +dataPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return array_map(function (string $filename) { + $content = file_get_contents($filename); + if ($content === false) { + throw new InternalServerError('cannot read pages'); + } + $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); + return new MarkdownPage( + (int) substr($idAndTitle, 0, 2), + substr($idAndTitle, 3), + $content + ); + }, $files); + } + + public function byName(string $name): MarkdownPage + { + $pages = array_values( + array_filter( + $this->all(), + fn (MarkdownPage $p) => $p->title === $name, + ) + ); + + if (count($pages) !== 1) { + throw new NotFound; + } + + return $pages[0]; + } +} diff --git a/app/src/Repository/MarkdownPageRepo.php b/app/src/Repository/MarkdownPageRepo.php index 3f80899..0792d32 100644 --- a/app/src/Repository/MarkdownPageRepo.php +++ b/app/src/Repository/MarkdownPageRepo.php @@ -2,18 +2,14 @@ namespace Lubian\NoFramework\Repository; +use Lubian\NoFramework\Exception\NotFound; use Lubian\NoFramework\Model\MarkdownPage; interface MarkdownPageRepo { - /** - * @return MarkdownPage[] - */ + /** @return MarkdownPage[] */ public function all(): array; - public function byId(int $id): MarkdownPage; - - public function byTitle(string $title): MarkdownPage; - - public function save(MarkdownPage $page): MarkdownPage; + /** @throws NotFound */ + public function byName(string $name): MarkdownPage; } diff --git a/app/src/Settings.php b/app/src/Settings.php index a6e4218..885aa7b 100644 --- a/app/src/Settings.php +++ b/app/src/Settings.php @@ -4,10 +4,6 @@ namespace Lubian\NoFramework; final class Settings { - /** - * @param array{driver: string, user: string, password: string, path: string} $connection - * @param array{devMode: bool, metadataDirs: string[], cacheDir: string} $doctrine - */ public function __construct( public readonly string $environment, public readonly string $dependenciesFile, @@ -15,19 +11,6 @@ final class Settings public readonly string $templateDir, public readonly string $templateExtension, public readonly string $pagesPath, - /** - * @var array{driver: string, user: string, password: string, path: string} - */ - public readonly array $connection, - /** - * @var array{devMode: bool, metadataDirs: string[], cacheDir: string} - */ - public readonly array $doctrine, ) { } - - public function isDev(): bool - { - return $this->environment === 'dev'; - } } diff --git a/app/templates/page/show.html b/app/templates/page/show.html index ebe707a..abe295e 100644 --- a/app/templates/page/show.html +++ b/app/templates/page/show.html @@ -1,13 +1,17 @@ - - - {{title}} - - - -
- {{{content}}} -
- + + + {{title}} + + + + + + +
+ {{{content}}} +
+ \ No newline at end of file diff --git a/implementation/15-adding-content/.php-cs-fixer.php b/implementation/15-adding-content/.php-cs-fixer.php new file mode 100644 index 0000000..705a7d7 --- /dev/null +++ b/implementation/15-adding-content/.php-cs-fixer.php @@ -0,0 +1,38 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config' + ]) + ); \ No newline at end of file diff --git a/implementation/15-adding-content/.phpcs.xml.dist b/implementation/15-adding-content/.phpcs.xml.dist new file mode 100644 index 0000000..3b433f6 --- /dev/null +++ b/implementation/15-adding-content/.phpcs.xml.dist @@ -0,0 +1,9 @@ + + + + + src + config + + + \ No newline at end of file diff --git a/implementation/15-adding-content/cli-config.php b/implementation/15-adding-content/cli-config.php new file mode 100644 index 0000000..fbc6598 --- /dev/null +++ b/implementation/15-adding-content/cli-config.php @@ -0,0 +1,13 @@ +getContainer(); + +return ConsoleRunner::createHelperSet($container->get(EntityManagerInterface::class)); diff --git a/implementation/15-adding-content/composer.json b/implementation/15-adding-content/composer.json new file mode 100644 index 0000000..4809539 --- /dev/null +++ b/implementation/15-adding-content/composer.json @@ -0,0 +1,56 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3", + "mustache/mustache": "^2.14", + "psr/http-server-middleware": "^1.0", + "middlewares/trailing-slash": "^2.0", + "middlewares/whoops": "^2.0", + "erusev/parsedown": "^1.7", + "symfony/cache": "^6.0", + "doctrine/orm": "^2.11", + "league/commonmark": "^2.2" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require-dev": { + "phpstan/phpstan": "^1.5", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0", + "squizlabs/php_codesniffer": "^3.6", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.1", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "mnapoli/hard-mode": "^0.3.0" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1234 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + } +} diff --git a/implementation/15-adding-content/composer.lock b/implementation/15-adding-content/composer.lock new file mode 100644 index 0000000..648f2d5 --- /dev/null +++ b/implementation/15-adding-content/composer.lock @@ -0,0 +1,4246 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "1e8469bfebe6479a139b946b8aba49de", + "packages": [ + { + "name": "dflydev/dot-access-data", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^3.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" + }, + "time": "2021-08-13T13:06:58+00:00" + }, + { + "name": "doctrine/cache", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/331b4d5dbaeab3827976273e9356b3b453c300ce", + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce", + "shasum": "" + }, + "require": { + "php": "~7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "alcaeus/mongo-php-adapter": "^1.1", + "cache/integration-tests": "dev-master", + "doctrine/coding-standard": "^8.0", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "predis/predis": "~1.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.2 || ^6.0@dev", + "symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", + "keywords": [ + "abstraction", + "apcu", + "cache", + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" + ], + "support": { + "issues": "https://github.com/doctrine/cache/issues", + "source": "https://github.com/doctrine/cache/tree/2.1.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "type": "tidelift" + } + ], + "time": "2021-07-17T14:49:29+00:00" + }, + { + "name": "doctrine/collections", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/1958a744696c6bb3bb0d28db2611dc11610e78af", + "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af", + "shasum": "" + }, + "require": { + "php": "^7.1.3 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5", + "vimeo/psalm": "^4.2.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", + "keywords": [ + "array", + "collections", + "iterators", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/collections/issues", + "source": "https://github.com/doctrine/collections/tree/1.6.8" + }, + "time": "2021-08-10T18:51:53+00:00" + }, + { + "name": "doctrine/common", + "version": "3.2.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "295082d3750987065912816a9d536c2df735f637" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/295082d3750987065912816a9d536c2df735f637", + "reference": "295082d3750987065912816a9d536c2df735f637", + "shasum": "" + }, + "require": { + "doctrine/persistence": "^2.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^1.4.1", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.0", + "symfony/phpunit-bridge": "^4.0.5", + "vimeo/psalm": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", + "homepage": "https://www.doctrine-project.org/projects/common.html", + "keywords": [ + "common", + "doctrine", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/common/issues", + "source": "https://github.com/doctrine/common/tree/3.2.2" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", + "type": "tidelift" + } + ], + "time": "2022-02-02T09:15:57+00:00" + }, + { + "name": "doctrine/dbal", + "version": "3.3.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "83f779beaea1893c0bece093ab2104c6d15a7f26" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/83f779beaea1893c0bece093ab2104c6d15a7f26", + "reference": "83f779beaea1893c0bece093ab2104c6d15a7f26", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/cache": "^1.11|^2.0", + "doctrine/deprecations": "^0.5.3", + "doctrine/event-manager": "^1.0", + "php": "^7.3 || ^8.0", + "psr/cache": "^1|^2|^3", + "psr/log": "^1|^2|^3" + }, + "require-dev": { + "doctrine/coding-standard": "9.0.0", + "jetbrains/phpstorm-stubs": "2021.1", + "phpstan/phpstan": "1.4.6", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "9.5.16", + "psalm/plugin-phpunit": "0.16.1", + "squizlabs/php_codesniffer": "3.6.2", + "symfony/cache": "^5.2|^6.0", + "symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0", + "vimeo/psalm": "4.22.0" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\DBAL\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", + "homepage": "https://www.doctrine-project.org/projects/dbal.html", + "keywords": [ + "abstraction", + "database", + "db2", + "dbal", + "mariadb", + "mssql", + "mysql", + "oci8", + "oracle", + "pdo", + "pgsql", + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlite", + "sqlserver", + "sqlsrv" + ], + "support": { + "issues": "https://github.com/doctrine/dbal/issues", + "source": "https://github.com/doctrine/dbal/tree/3.3.4" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", + "type": "tidelift" + } + ], + "time": "2022-03-20T18:37:29+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "v0.5.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "9504165960a1f83cc1480e2be1dd0a0478561314" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314", + "reference": "9504165960a1f83cc1480e2be1dd0a0478561314", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0|^7.0|^8.0", + "phpunit/phpunit": "^7.0|^8.0|^9.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v0.5.3" + }, + "time": "2021-03-21T12:59:47+00:00" + }, + { + "name": "doctrine/event-manager", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": "<2.9@dev" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/1.1.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2020-05-29T18:28:51+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "vimeo/psalm": "^4.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2021-10-22T20:16:43+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-03-03T08:28:38+00:00" + }, + { + "name": "doctrine/lexer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2022-02-28T11:07:21+00:00" + }, + { + "name": "doctrine/orm", + "version": "2.11.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/orm.git", + "reference": "9c351e044478135aec1755e2c0c0493a4b6309db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/orm/zipball/9c351e044478135aec1755e2c0c0493a4b6309db", + "reference": "9c351e044478135aec1755e2c0c0493a4b6309db", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/cache": "^1.12.1 || ^2.1.1", + "doctrine/collections": "^1.5", + "doctrine/common": "^3.0.3", + "doctrine/dbal": "^2.13.1 || ^3.2", + "doctrine/deprecations": "^0.5.3", + "doctrine/event-manager": "^1.1", + "doctrine/inflector": "^1.4 || ^2.0", + "doctrine/instantiator": "^1.3", + "doctrine/lexer": "^1.0", + "doctrine/persistence": "^2.2", + "ext-ctype": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3", + "symfony/console": "^3.0 || ^4.0 || ^5.0 || ^6.0", + "symfony/polyfill-php72": "^1.23", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "doctrine/annotations": "<1.13 || >= 2.0" + }, + "require-dev": { + "doctrine/annotations": "^1.13", + "doctrine/coding-standard": "^9.0", + "phpbench/phpbench": "^0.16.10 || ^1.0", + "phpstan/phpstan": "1.4.6", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "squizlabs/php_codesniffer": "3.6.2", + "symfony/cache": "^4.4 || ^5.4 || ^6.0", + "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0", + "vimeo/psalm": "4.22.0" + }, + "suggest": { + "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0", + "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" + }, + "bin": [ + "bin/doctrine" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\ORM\\": "lib/Doctrine/ORM" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "Object-Relational-Mapper for PHP", + "homepage": "https://www.doctrine-project.org/projects/orm.html", + "keywords": [ + "database", + "orm" + ], + "support": { + "issues": "https://github.com/doctrine/orm/issues", + "source": "https://github.com/doctrine/orm/tree/2.11.2" + }, + "time": "2022-03-09T15:23:58+00:00" + }, + { + "name": "doctrine/persistence", + "version": "2.4.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/persistence.git", + "reference": "092a52b71410ac1795287bb5135704ef07d18dd0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/092a52b71410ac1795287bb5135704ef07d18dd0", + "reference": "092a52b71410ac1795287bb5135704ef07d18dd0", + "shasum": "" + }, + "require": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/collections": "^1.0", + "doctrine/deprecations": "^0.5.3", + "doctrine/event-manager": "^1.0", + "php": "^7.1 || ^8.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0" + }, + "conflict": { + "doctrine/annotations": "<1.0 || >=2.0", + "doctrine/common": "<2.10" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.11", + "doctrine/annotations": "^1.0", + "doctrine/coding-standard": "^9.0", + "doctrine/common": "^3.0", + "phpstan/phpstan": "1.4.6", + "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6.0", + "vimeo/psalm": "4.21.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "src/Common", + "Doctrine\\Persistence\\": "src/Persistence" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", + "homepage": "https://doctrine-project.org/projects/persistence.html", + "keywords": [ + "mapper", + "object", + "odm", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/persistence/issues", + "source": "https://github.com/doctrine/persistence/tree/2.4.1" + }, + "time": "2022-03-22T06:44:40+00:00" + }, + { + "name": "erusev/parsedown", + "version": "1.7.4", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "support": { + "issues": "https://github.com/erusev/parsedown/issues", + "source": "https://github.com/erusev/parsedown/tree/1.7.x" + }, + "time": "2019-12-30T22:54:17+00:00" + }, + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-03-29T20:12:16+00:00" + }, + { + "name": "league/commonmark", + "version": "2.2.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", + "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.15" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.30.0", + "commonmark/commonmark.js": "0.30.0", + "composer/package-versions-deprecated": "^1.8", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4", + "phpstan/phpstan": "^0.12.88 || ^1.0.0", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2022-02-26T21:24:45+00:00" + }, + { + "name": "league/config", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.90", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2021-08-14T12:15:32+00:00" + }, + { + "name": "middlewares/trailing-slash", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/middlewares/trailing-slash.git", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", + "shasum": "" + }, + "require": { + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to normalize the trailing slash of the uri path", + "homepage": "https://github.com/middlewares/trailing-slash", + "keywords": [ + "http", + "middleware", + "normalize", + "path", + "psr-15", + "psr-7", + "slash" + ], + "support": { + "issues": "https://github.com/middlewares/trailing-slash/issues", + "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" + }, + "time": "2020-12-02T00:06:55+00:00" + }, + { + "name": "middlewares/utils", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/middlewares/utils.git", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v2.16", + "guzzlehttp/psr7": "^2.0", + "laminas/laminas-diactoros": "^2.4", + "nyholm/psr7": "^1.0", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "slim/psr7": "^1.4", + "squizlabs/php_codesniffer": "^3.5", + "sunrise/http-message": "^1.0", + "sunrise/http-server-request": "^1.0", + "sunrise/stream": "^1.0.15", + "sunrise/uri": "^1.0.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\Utils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Common utils for PSR-15 middleware packages", + "homepage": "https://github.com/middlewares/utils", + "keywords": [ + "PSR-11", + "http", + "middleware", + "psr-15", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/middlewares/utils/issues", + "source": "https://github.com/middlewares/utils/tree/v3.3.0" + }, + "time": "2021-07-04T17:56:23+00:00" + }, + { + "name": "middlewares/whoops", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/middlewares/whoops.git", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.5", + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^5.0 || ^7.0", + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to use Whoops as error handler", + "homepage": "https://github.com/middlewares/whoops", + "keywords": [ + "error", + "http", + "middleware", + "psr-15", + "psr-7", + "server", + "whoops" + ], + "support": { + "issues": "https://github.com/middlewares/whoops/issues", + "source": "https://github.com/middlewares/whoops/tree/v2.0.2" + }, + "time": "2022-01-27T20:31:30+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nette/schema", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", + "shasum": "" + }, + "require": { + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": ">=7.1 <8.2" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "phpstan/phpstan-nette": "^0.12", + "tracy/tracy": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.2.2" + }, + "time": "2021-10-15T11:40:02+00:00" + }, + { + "name": "nette/utils", + "version": "v3.2.7", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", + "shasum": "" + }, + "require": { + "php": ">=7.2 <8.2" + }, + "conflict": { + "nette/di": "<3.0.6" + }, + "require-dev": { + "nette/tester": "~2.0", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v3.2.7" + }, + "time": "2022-01-24T11:29:14+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/master" + }, + "time": "2018-10-30T17:12:04+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "symfony/cache", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", + "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^1.1.7|^2|^3", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/var-exporter": "^5.4|^6.0" + }, + "conflict": { + "doctrine/dbal": "<2.13.1", + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/var-dumper": "<5.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^2.13.1|^3.0", + "predis/predis": "^1.1", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/2f7463f156cf9c665d9317e21a809c3bbff5754e", + "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "psr/cache": "^3.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.0.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-08-17T15:35:52+00:00" + }, + { + "name": "symfony/console", + "version": "v6.0.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "3bebf4108b9e07492a2a4057d207aa5a77d146b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/3bebf4108b9e07492a2a4057d207aa5a77d146b1", + "reference": "3bebf4108b9e07492a2a4057d207aa5a77d146b1", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.4|^6.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/lock": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.0.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-02-25T10:48:52+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-07-12T14:48:14+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "30885182c981ab175d4d034db0f6f469898070ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", + "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-10-20T20:35:02+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "81b86b50cf841a64252b439e738e97f4a34e2783" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", + "reference": "81b86b50cf841a64252b439e738e97f4a34e2783", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-23T21:10:46+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T09:17:38+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-04T08:16:47+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-04T16:48:04+00:00" + }, + { + "name": "symfony/string", + "version": "v6.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/522144f0c4c004c80d56fa47e40e17028e2eefc2", + "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.0" + }, + "require-dev": { + "symfony/error-handler": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/translation-contracts": "^2.0|^3.0", + "symfony/var-exporter": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v6.0.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:55:41+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "130229a482abf17635a685590958894dfb4b4360" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/130229a482abf17635a685590958894dfb4b4360", + "reference": "130229a482abf17635a685590958894dfb4b4360", + "shasum": "" + }, + "require": { + "php": ">=8.0.2" + }, + "require-dev": { + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "doctrine/coding-standard", + "version": "8.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/coding-standard.git", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Müller", + "email": "st.mueller@dzh-online.de" + } + ], + "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "doctrine", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/doctrine/coding-standard/issues", + "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" + }, + "time": "2021-04-03T10:54:55+00:00" + }, + { + "name": "mnapoli/hard-mode", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/hard-mode.git", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", + "shasum": "" + }, + "require": { + "doctrine/coding-standard": "^8.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Hard mode for PHP", + "support": { + "issues": "https://github.com/mnapoli/hard-mode/issues", + "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" + }, + "time": "2020-10-12T07:54:37+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.5.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/39953ac1452a8843702ee41a35b4861d3e8207a7", + "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.5.3" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-30T21:55:08+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^1.2.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" + }, + "time": "2021-11-18T09:30:29+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/15-adding-content/config/dependencies.php b/implementation/15-adding-content/config/dependencies.php new file mode 100644 index 0000000..e2a3925 --- /dev/null +++ b/implementation/15-adding-content/config/dependencies.php @@ -0,0 +1,60 @@ + fn (SystemClockNow $n) => $n, + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, + Emitter::class => fn (BasicEmitter $e) => $e, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, + CacheInterface::class => fn (FilesystemAdapter $a) => $a, + MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, + MarkdownParser::class => fn (ParsedownParser $p) => $p, + + // Factories + ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), + Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + MarkdownPageFilesystem::class => fn (Settings $s) => new MarkdownPageFilesystem($s->pagesPath), + CachedMarkdownPageRepo::class => fn (CacheInterface $c, MarkdownPageFilesystem $r, Settings $s) => new CachedMarkdownPageRepo($c, $r, $s), + EntityManagerInterface::class => fn (DoctrineEm $f) => $f->create(), +]; diff --git a/implementation/15-adding-content/config/middlewares.php b/implementation/15-adding-content/config/middlewares.php new file mode 100644 index 0000000..71dd461 --- /dev/null +++ b/implementation/15-adding-content/config/middlewares.php @@ -0,0 +1,11 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/page', [Page::class, 'list']); + $r->addRoute('GET', '/page/{page}', [Page::class, 'show']); + $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); + $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/implementation/15-adding-content/config/settings.php b/implementation/15-adding-content/config/settings.php new file mode 100644 index 0000000..5c58216 --- /dev/null +++ b/implementation/15-adding-content/config/settings.php @@ -0,0 +1,23 @@ + 'pdo_sqlite', + 'user' => '', + 'password' => '', + 'path' => __DIR__ . '/../data/db.sqlite', + ], + doctrine: [ + 'devMode' => true, + 'metadataDirs' => [__DIR__ . '/../src/Model/'], + 'cacheDir' => __DIR__ . '/../data/cache/', + ], +); diff --git a/implementation/15-adding-content/data/pages/01-front-controller.md b/implementation/15-adding-content/data/pages/01-front-controller.md new file mode 100644 index 0000000..87a12ad --- /dev/null +++ b/implementation/15-adding-content/data/pages/01-front-controller.md @@ -0,0 +1,53 @@ +[next >>](02-composer.md) + +### Front Controller + +A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. + +To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. + +A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. + +The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. + +But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. + +So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. + +Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: + +```php +>](02-composer.md) diff --git a/implementation/15-adding-content/data/pages/02-composer.md b/implementation/15-adding-content/data/pages/02-composer.md new file mode 100644 index 0000000..a25a4a8 --- /dev/null +++ b/implementation/15-adding-content/data/pages/02-composer.md @@ -0,0 +1,75 @@ +[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) + +### Composer + +[Composer](https://getcomposer.org/) is a dependency manager for PHP. + +Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do +something. With Composer, you can install third-party libraries for your application. + +If you don't have Composer installed already, head over to the website and install it. You can find Composer packages +for your project on [Packagist](https://packagist.org/). + +Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will +be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. + +Add the following content to the file: + +```json +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubiana", + "email": "lubiana@hannover.ccc.de" + } + ] +} +``` + +In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use +whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. +Just replace it with your namespace in your own code. + +I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. + +As the Bootstrap.php file is placed in that directory we should +add the namespace to the File as well. Here is my current Bootstrap.php +as a reference: + +```php +>](03-error-handler.md) diff --git a/implementation/15-adding-content/data/pages/03-error-handler.md b/implementation/15-adding-content/data/pages/03-error-handler.md new file mode 100644 index 0000000..60465d0 --- /dev/null +++ b/implementation/15-adding-content/data/pages/03-error-handler.md @@ -0,0 +1,79 @@ +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) + +### Error Handler + +An error handler allows you to customize what happens if your code results in an error. + +A nice error page with a lot of information for debugging goes a long way during development. So the first package +for your application will take care of that. + +I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. +If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, +you have total control over your project. + +An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) + +To install a new package, open up your `composer.json` and add the package to the require part. It should now look +like this: + +```php +"require": { + "php": ">=8.1.0", + "filp/whoops": "^2.14" +}, +``` + +Now run `composer update` in your console and it will be installed. + +Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, +i that case composer automatically installs the package and updates your composer.json-file. + +But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, +ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you +only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. + +**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message +can help someone to gain access to your system. Always show a user friendly error page instead and send an email to +yourself, write to a log or something similar. So only you can see the errors in the production environment. + +For development that does not make sense though -- you want a nice error page. The solution is to have an environment +switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in +case no environment has been set. + +Then after the error handler registration, throw an `Exception` to test if everything is working correctly. +Your `Bootstrap.php` should now look similar to this: + +```php +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (\Throwable $e) { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +throw new \Exception("Ooooopsie"); + +``` + +You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until +you get it working. Now would also be a good time for another commit. + + +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/implementation/15-adding-content/data/pages/04-development-helpers.md b/implementation/15-adding-content/data/pages/04-development-helpers.md new file mode 100644 index 0000000..74f913c --- /dev/null +++ b/implementation/15-adding-content/data/pages/04-development-helpers.md @@ -0,0 +1,260 @@ +[<< previous](03-error-handler.md) | [next >>](05-http.md) + +### Development Helpers + +I have added some more helpers to my composer.json that help me with development. As these are scripts and programms +used only for development they should not be used in a production environment. Composer has a specific sections in its +file called "dev-dependencies", everything that is required in this section does not get installen in production. + +Let's install our dev-helpers and i will explain them one by one: +`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` + +#### Static Code Analysis with phpstan + +Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or +create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements +that are not (or not always) available, if-statements that always are true and a lot of other stuff. + +A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. + +```php +/** + * @param \DateTime $date + * @return void + */ +function printDate($date) { + $date->format('Y-m-d H:i:s'); +} + +printDate('now'); +``` +if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` + +It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has +no use, and secondly, that we are calling the function with a string instead of a datetime object. + +```shell +1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + ------ --------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ --------------------------------------------------------------------------------------------- +30 Call to method DateTime::format() on a separate line has no effect. +33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. + ------ --------------------------------------------------------------------------------------------- +``` + +The second error is something that "declare strict-types" already catches for us, but the first error is something that +we usually would not discover easily without speccially looking for this errortype. + +We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and +path everytime we want to check our code for errors: + +```yaml +parameters: + level: max + paths: + - src +``` +now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project + +With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us +some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. + +```shell +composer require --dev phpstan/extension-installer +composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules +``` + +During the first install you need to allow the extension installer to actually install the extension. The second command +installs some more strict rulesets and activates them in phpstan. + +If we now rerun phpstan it already tells us about some errors we have made: + +``` + ------ ----------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ ----------------------------------------------------------------------------------------------- +10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider + using long ternary. +25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: + http://bit.ly/subtypeexception +26 Unreachable statement - code above always terminates. + ------ ----------------------------------------------------------------------------------------------- +``` + +The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove +that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific +problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working +on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages +everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we +are adding to our code. + +In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the +phpstan.neon file and run phpstan with the +'--generate-baseline' option: + +```yaml +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` +```shell +[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline +Note: Using configuration file /home/vagrant/app/phpstan.neon. + 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + + + [OK] Baseline generated with 1 error. + + +``` + +you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) + +#### PHP-CS-Fixer + +Another great tool is the php-cs-fixer, which just applies a specific style to your code. + +when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current +directory. + +You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) + +personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup +a configuration file that i use in all my projects .php-cs-fixer.php: + +```php +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + ]) + ); +``` + +#### PHP Codesniffer + +The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra +rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. + +it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. + +Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have +guessed we are adding some extra rules. + +Lets install the ruleset with composer +```shell +composer require --dev mnapoli/hard-mode +``` + +and add a configuration file to actually use it '.phpcs.xml.dist' +```xml + + + + + src + + + +``` + +running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. + +``` +[vagrant@archlinux app]$ ./vendor/bin/phpcs + +FILE: src/Bootstrap.php +---------------------------------------------------------------------------------------------------- +FOUND 4 ERRORS AFFECTING 4 LINES +---------------------------------------------------------------------------------------------------- + 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. + 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead +---------------------------------------------------------------------------------------------------- +PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY +---------------------------------------------------------------------------------------------------- + +Time: 639ms; Memory: 10MB +``` + +You can then use `./vendor/bin/phpcbf` to try to fix them + + +#### Symfony Var-Dumper + +another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small +functions. + +dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects +or arrays. + +dd() on the other hand is a function that dumps its parameters and then exits the php-script. + +you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. + +#### Composer scripts + +now we have a few commands that are available on the command line. i personally do not like to type complex commands +with lots of parameters by hand all the time, so i added a few lines to my composer.json: + +```json +"scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" +}, +``` + +that way i can just type "composer" followed by the command name in the root of my project. if i want to start the +php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the +time. + +You could also configure PhpStorm to automatically run these commands in the background and highlight the violations +directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my +flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. + +My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before +commiting and pushing the code. + +[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/implementation/15-adding-content/data/pages/05-http.md b/implementation/15-adding-content/data/pages/05-http.md new file mode 100644 index 0000000..6166214 --- /dev/null +++ b/implementation/15-adding-content/data/pages/05-http.md @@ -0,0 +1,124 @@ +[<< previous](04-development-helpers.md) | [next >>](06-router.md) + +### HTTP + +PHP already has a few things built in to make working with HTTP easier. For example there are the +[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. + +These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, +if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, +then you will want a class with a nice object-oriented interface that you can use in your application instead. + +Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The +standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php +projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. + +As this is a widely adopted standard there are already several implementations available for us to use. I will choose +the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. + +Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a +[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. + +Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use +that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding +the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). + + +to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit +enter + +Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): + +```php +$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); +$response = new \Laminas\Diactoros\Response; +$response->getBody()->write('Hello World! '); +$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); +``` + +This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. + +In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the +write()-Method on that object. + + +To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: + +```php +echo $response->getBody(); +``` + +This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only +stores data. + +You can play around with the other methods of the Request object and take a look at its content with the dd() function. + +```php +dd($response) +``` + +Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot +be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which +creates a copy of the request object with the changed property and returns that clone: + +```php +$response = $response->withStatus(200); +$response = $response->withAddedHeader('Content-type', 'application/json'); +``` + +If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been +defined this way. + +But if you have been keeping attention you might argue that the following line should not work if the request object is +immutable. + +```php +$response->getBody()->write('Hello World!'); +``` + +The response-body implements a stream interface which is immutable for some reasons that are described in the +[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be +aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in +the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. +I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content +to a response object. + +```php +$body = $response->getBody(); +$body->write('Hello World!'); +$response = $response->withBody($body); +``` + +Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our +output-logic a little bit more. Replace the line that echos the response-body with the following: + +```php +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); +``` + +This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a +webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. + +Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. + +Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter + + +[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/implementation/15-adding-content/data/pages/06-router.md b/implementation/15-adding-content/data/pages/06-router.md new file mode 100644 index 0000000..6c39ae5 --- /dev/null +++ b/implementation/15-adding-content/data/pages/06-router.md @@ -0,0 +1,101 @@ +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) + +### Router + +A router dispatches to different handlers depending on rules that you have set up. + +With your current setup it does not matter what URL is used to access the application, it will always result in the same +response. So let's fix that now. + +I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own +favorite package. + +Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) + +By now you know how to install Composer packages, so I will leave that to you. + +Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. + +```php +$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +switch ($routeInfo[0]) { + case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: + $response = (new \Laminas\Diactoros\Response)->withStatus(405); + $response->getBody()->write('Method not allowed'); + $response = $response->withStatus(405); + break; + case \FastRoute\Dispatcher::FOUND: + $handler = $routeInfo[1]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + /** @var \Psr\Http\Message\ResponseInterface $response */ + $response = call_user_func($handler, $request); + break; + case \FastRoute\Dispatcher::NOT_FOUND: + default: + $response = (new \Laminas\Diactoros\Response)->withStatus(404); + $response->getBody()->write('Not Found!'); + break; +} +``` + +In the first part of the code, you are registering the available routes for your application. In the second part, the +dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, +we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. +If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. + +This setup might work for really small applications, but once you start adding a few routes your bootstrap file will +quickly get cluttered. So let's move them out into a separate file. + +Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; + +```php +addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}; +``` + +Now let's rewrite the route dispatcher part to use the `Routes.php` file. + +```php +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); +``` + +This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. + +Of course we now need to add the 'config' folder to the configuration files of our +devhelpers so that they can scan that directory as well. + +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/implementation/15-adding-content/data/pages/07-dispatching-to-a-class.md b/implementation/15-adding-content/data/pages/07-dispatching-to-a-class.md new file mode 100644 index 0000000..0c961a4 --- /dev/null +++ b/implementation/15-adding-content/data/pages/07-dispatching-to-a-class.md @@ -0,0 +1,137 @@ +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) + +### Dispatching to a Class + +In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). +MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to +learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) +and the followup posts. + +So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). + +We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other +common names are 'Controllers' or 'Actions'. + +Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. +In there, create a `Hello.php` file. + +```php +getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + } +} +``` + +You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) +that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is +fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement +it in some other parts of our application as well. In order to use that Interface we have to require it with composer: +'composer require psr/http-server-handler'. + +The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. +At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. + +Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: + +```php +return function(\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); + $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); +}; +``` + +Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared +the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing +the response to the one we defined for the second route. + +To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: + +```php +case \FastRoute\Dispatcher::FOUND: + $handler = new $routeInfo[1]; + if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { + throw new \Exception('Invalid Requesthandler'); + } + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + assert($response instanceof \Psr\Http\Message\ResponseInterface) + break; +``` + +So instead of just calling a method you are now instantiating an object and then calling the method on it. + +Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. + +And of course don't forget to commit your changes. + +Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still +generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for +example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still +have our whoopsie error-handler but i like to be more explicit in my control flow. + +In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new +Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and +InternalServerError. All three should extend phps Base Exception class. + +Here is my NotFound.php for example. + +```php + $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody()->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} +``` + +Check if our code still works, try to trigger some errors, run phpstan and the fix command +and don't forget to commit your changes. + +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/implementation/15-adding-content/data/pages/08-inversion-of-control.md b/implementation/15-adding-content/data/pages/08-inversion-of-control.md new file mode 100644 index 0000000..21f4f23 --- /dev/null +++ b/implementation/15-adding-content/data/pages/08-inversion-of-control.md @@ -0,0 +1,54 @@ +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) + +### Inversion of Control + +In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we +want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then +we would need to edit every one of our request handlers to call a different constructor of the class. + +The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that +instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done +with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). + +If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is +implemented, it will make sense. + +Change your `Hello` action to the following: + +```php +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: + +```php +$handler = new $className($response); +``` + +Of course we need to also update all the other handlers. + +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/implementation/15-adding-content/data/pages/09-dependency-injector.md b/implementation/15-adding-content/data/pages/09-dependency-injector.md new file mode 100644 index 0000000..7f7c6a2 --- /dev/null +++ b/implementation/15-adding-content/data/pages/09-dependency-injector.md @@ -0,0 +1,213 @@ +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) + +### Dependency Injector + +A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when +the class is instantiated. + +Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work +with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look +for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). + +I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) +out of the box. + +After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: + +```php +addDefinitions([ + \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), +]); + +return $builder->build(); +``` + +In this file we create a containerbuilder, add some definitions to it and return the container. +As the container supports autowiring we only need to define services where we want to use a specific implementation of +an interface. + +In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to +tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second +exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. + +Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), +as we will use that extensively. + +Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally +to create our Handler-Object + +```php +$container = require __DIR__ . '/../config/dependencies.php'; +assert($container instanceof \Psr\Container\ContainerInterface); + +$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); +assert($request instanceof \Psr\Http\Message\ServerRequestInterface); +``` + +The other part that has to be changed is the dispatching of the route. Before you had the following code: + +```php +$className = $routeInfo[1]; +$handler = new $className($response); +assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Change that to the following: + +```php +/** @var RequestHandlerInterface $handler */ +$className = $routeInfo[1]; +$handler = $container->get($className); +assert($handler instanceof RequestHandlerInterface); +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Make sure to use the container fetch the response object in the catch blocks as well: + +```php +} catch (MethodNotAllowed) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(404); + $response->getBody()->write('Not Found'); +} +``` + +Now all your controller constructor dependencies will be automatically resolved with PHP-DI. + +We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons +in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call +`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we +want to work with a different date in a test. + +Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation +that creates us a DateTimeImmutable object with the current date and time. + +src/Service/Time/Now.php: +```php +namespace Lubian\NoFramework\Service\Time; + +interface Now +{ + public function __invoke(): \DateTimeImmutable; +} +``` +src/Service/Time/SystemClockNow.php: +```php +namespace Lubian\NoFramework\Service\Time; + +final class SystemClockNow implements Now +{ + + public function __invoke(): \DateTimeImmutable + { + return new \DateTimeImmutable; + } +} +``` +If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and +update the handle-method to use the new class property: + +```php +getAttribute('name', 'Stranger'); + $nowAsString = ($this->now)()->format('H:i:s'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowAsString); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI +automatically figures out what classes are requested in the constructor and tries to create the objects needed. + +But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred +S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: + +```php + public function __construct( + private ResponseInterface $response, + private Now $now, + ) +``` + +When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation +should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: + +```php +\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), +``` + +we could also use the PHP-DI create method to delegate the object creation to the container implementation: +```php +\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), +``` + +this way the container can try to resolve any dependencies that the class might have internally, but prefer the other +method because we are not depending on this specific dependency injection implementation. + +Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are +requesting the Hello action. + +If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As +we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way +we wont get any warnings for this particular issue, but for any other issues we add to our code. + +Update the phpstan.neon file to include a "baseline" file: + +``` +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` + +if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and +ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just +'baseline' + +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/implementation/15-adding-content/data/pages/10-invoker.md b/implementation/15-adding-content/data/pages/10-invoker.md new file mode 100644 index 0000000..3033fae --- /dev/null +++ b/implementation/15-adding-content/data/pages/10-invoker.md @@ -0,0 +1,102 @@ +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) + +### Invoker + +Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the +one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long +with some services and not the whole Requestobject with all its various properties. + +If we take our Hello action for example we only need a response object, the time service and the 'name' information from +the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named +the class hello and it would be redundant to also call the the method hello. So an updated version of that class could +look like this: + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + string $name = 'Stranger', + ): ResponseInterface + { + $body = $this->response->getBody(); + $nowString = $now->get()->format('H:i:s'); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowString); + return $response + ->withBody($body) + ->withStatus(200); + } +} +``` + +It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short +closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the +rootpath of our application yet. + +```php +$r->addRoute('GET', '/hello[/{name}]', Hello::class); +$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); +$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +``` + +In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the +route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that +class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what +arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router +if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple +callable we would need to manually use reflection as well to resolve all the arguments. + +But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) +which handles all of that for us. + +After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo +switch section in the Bootstrap.php file to use the container->call() method: + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2]; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$response = $container->call($handler, $args); +``` + +Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. + +But by now you should know that I do not like to depend on specific implementations and the call method is not defined in +the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container +or any other implementation. + +Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific +container implementation so we could use it with any container that implements the ContainerInterface. And best of all +the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that +we could implement if we ever want to write our own implementation or we could write an adapter that uses a different +class that solves the same problem. + +But for now we are using the solution provided by PHP-DI. +So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2] ?? []; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$invoker = $container->get(InvokerInterface::class); +assert($invoker instanceof InvokerInterface); +$response = $invoker->call($handler, $args); +assert($response instanceof ResponseInterface); +``` + +Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) +by php, and even some more. + +But let us move on to something more fun and add some templating functionality to our application as we are trying to build +a website in the end. + +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/implementation/15-adding-content/data/pages/11-templating.md b/implementation/15-adding-content/data/pages/11-templating.md new file mode 100644 index 0000000..3759664 --- /dev/null +++ b/implementation/15-adding-content/data/pages/11-templating.md @@ -0,0 +1,240 @@ +[<< previous](10-invoker.md) | [next >>](12-configuration.md) + +### Templating + +A template engine is not necessary with PHP because the language itself can take care of that. But it can make things +like escaping values easier. They also make it easier to draw a clear line between your application logic and the +template files which should only put your variables into the HTML code. + +A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please +also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. +Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. + +For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install +that package before you continue (`composer require mustache/mustache`). + +Another well known alternative would be [Twig](http://twig.sensiolabs.org/). + +Now please go and have a look at the source code of the +[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class +does not implement an interface. + +You could just type hint against the concrete class. But the problem with this approach is that you create tight +coupling. + +In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the +implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want +to add functionality to the engine. You can't do that without going back and changing all your code that is tightly +coupled. + +What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need +another implementation, you just implement that interface in your new class and inject the new class instead. + +Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). +This sounds a lot more complicated than it is, so just follow along. + +First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). +This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. +A class can implement multiple interfaces if necessary. + +So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a +new folder in your `src/` folder with the name `Template` where you can put all the template related things. + +In there create a new interface `Renderer.php` that looks like this: + +```php + $data + * @return string + */ + public function render(string $template, array $data = []) : string; +} +``` + +Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file +`MustacheRenderer.php` with the following content: + +```php +engine->render($template, $data); + } +} +``` + +As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple +and only fulfills the interface. + +Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know +which implementation he has to inject when you hint for the interface. Add this line: + +```php +[ + ... + \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) + ->constructor(new Mustache_Engine), +] +``` + +Now update the Hello.php class to require an implementation of our renderer interface +and use that to render a string using mustache syntax. + + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + Renderer $renderer, + string $name = 'Stranger', + ): ResponseInterface { + $body = $response->getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render( + 'Hello {{name}}, the time is {{now}}!', + $data, + ); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} +``` + +Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. +But what we want is template files, so let's go back and change that. + +To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the +`dependencies.php` file and add the following code: + +```php +[ + ... + Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), + Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), +] +``` + +We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. +Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have +to rename all the template files. + +To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the +dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader +in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object +we can then use it in the factory. + +In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: + +``` +

Hello World

+Hello {{ name }} +``` + +Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` + +Navigate to the hello page in your browser to make sure everything works. + +One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies +file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration +values. + +Lets create a 'Settings' class in our './src' Folder: + +```php +addDefinitions([ + Settings::class => fn () => require __DIR__ '/settings.php', + ResponseInterface::class => create(Response::class), + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + Renderer::class => fn (ME $me) => new Mustache($me), + MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), +]); + +return $builder->build(); +``` + + + +And as always, don't forget to commit your changes. + + +[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/implementation/15-adding-content/data/pages/12-configuration.md b/implementation/15-adding-content/data/pages/12-configuration.md new file mode 100644 index 0000000..a44dfd5 --- /dev/null +++ b/implementation/15-adding-content/data/pages/12-configuration.md @@ -0,0 +1,201 @@ +[<< previous](11-templating.md) | [next >>](13-refactoring.md) + +### Configuration + +In the last chapter we added some more definitions to our dependencies.php in that definitions +we needed to pass quite a few configuration settings and filesystem strings to the constructors +of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. + +As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. + +As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. + +Lets create a 'Settings' class in our './src' Folder: + +```php +filePath; + } +} +``` + +If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files +and craft a settings object from them. + +As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another +factory for our Container because everyone should have a Friend :) + +```php +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn () => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} +``` + +For this to work we need to change our dependencies.php file to just return the array of definitions: +And here we can instantly use the Settings object to create our template engine. + +```php + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), +]; +``` + +Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: +require __DIR__ . '/../vendor/autoload.php'; + +```php +... +error_reporting(E_ALL); + +$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); +$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); +... +``` + +Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. + +[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/implementation/15-adding-content/data/pages/13-refactoring.md b/implementation/15-adding-content/data/pages/13-refactoring.md new file mode 100644 index 0000000..067e168 --- /dev/null +++ b/implementation/15-adding-content/data/pages/13-refactoring.md @@ -0,0 +1,377 @@ +[<< previous](12-configuration.md) | [next >>](14-middleware.md) + +### Refactoring + +By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no +reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. +After all the bootstrap file should just set up the classes needed for the handling logic and execute them. + +At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. +As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the +method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non +static and extracted an interface. + +'./src/Http/Emitter.php' +```php +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + echo $response->getBody(); + } +} +``` +After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following +code in the Bootstrap.php to emit the response: + +```php +/** @var Emitter $emitter */ +$emitter = $container->get(Emitter::class); +$emitter->emit($response); +``` + +If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily +write an adapter that implements your emitter interface and wraps that more advanced emitter + +Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and +calling the routerhandler that in the passes the request to a function and gets the response. + +For this to steps to be seperated we are going to create two more classes: +1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object +2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the + requestobject, fetches the correct object from the container and calls it to create a response. + +Lets create the HandlerInterface first: + +```php +getAttribute($this->routeAttributeName, false); + assert($handler !== false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} + +``` + +We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. +The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler +as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in +the next chapter. + +```php +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} +``` + +Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. +```php +[ + '...', + Emitter::class => fn (BasicEmitter $e) => $e, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, +], +``` + +And then we can update our Bootstrap.php to fetch all the services and let them handle the request. + +```php +... +$routeMiddleWare = $container->get(MiddlewareInterface::class); +assert($routeMiddleWare instanceof MiddlewareInterface); +$handler = $container->get(RoutedRequestHandler::class); +assert($handler instanceof RequestHandlerInterface); +$emitter = $container->get(Emitter::class); +assert($emitter instanceof Emitter); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$response = $routeMiddleWare->process($request, $handler); +$emitter->emit($response); +``` +Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of +code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). + +So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. + +I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class +should require to function properly. + +* A RequestFactory + We want our Kernel to be able to build the request itself +* An Emitter + Without an Emitter we will not be able to send the response to the client +* RouteMiddleware + To decore the request with the correct handler for the requested route +* RequestHandler + To delegate the request to the correct funtion that creates the response + +As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface +to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our +interface: + +```php +factory::fromGlobals(); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} +``` + +For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: + +```php +routeMiddleware->process($request, $this->handler); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} + +``` + +We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines + +```php +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); +``` + +You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking +at the Whoops output and adding the needed definitions to the dependencies.php file. + +And as always, don't forget to commit your changes. + +[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/implementation/15-adding-content/data/pages/14-middleware.md b/implementation/15-adding-content/data/pages/14-middleware.md new file mode 100644 index 0000000..e698327 --- /dev/null +++ b/implementation/15-adding-content/data/pages/14-middleware.md @@ -0,0 +1,298 @@ +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) + +### Middleware + +In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain +a bit more about what this interface does and why it is used in many applications. + +The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets +passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all +the middlewars to the client/emitter. + +So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the +response after it gets created by our handlers. + +So lets take a look at the middleware and the requesthandler interfaces + +```php +interface MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; +} + +interface RequestHandlerInterface +{ + public function handle(ServerRequestInterface $request): ResponseInterface; +} +``` + +The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a +requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the +response. + +But the middleware could just ignore the handler and produce a response on its own as the interface just requires us +to produce a response. + +A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users +that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the +database. + +In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates +the request with an 'isAuthenticated' attribute. + +If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that +response is not already cached, than we let the handler create the response and store that in the cache for a few +seconds + +```php +interface CacheInterface +{ + public function get(string $key, callable $resolver, int $ttl): mixed; +} +``` + +The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one +defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses +the callable to produce the value and stores it for the time specified in ttl. + +so lets write our caching middleware: + +```php +final class CachingMiddleware implements MiddlewareInterface +{ + public function __construct(private CacheInterface $cache){} + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { + $key = $request->getUri()->getPath(); + return $this->cache->get($key, fn() => $handler->handle($request), 10); + } + return $handler->handle($request); + } +} +``` + +we can also modify the response after it has been created by our application, for example we could implement a gzip +middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser +know that our application is used to serve dank memes: + +```php +final class DankMemeMiddleware implements MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $response = $handler->handle($request); + return $response->withAddedHeader('Meme', 'Dank'); + } +} +``` + +but for our application we are going to just add two external middlewares: + +* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. +* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware + +```bash +composer require middlewares/trailing-slash +composer require middlewares/whoops +``` + +The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the +application as well as the middleware stack. + +Our desired request -> response flow looks something like this: + + Client + | ^ + v | + Kernel + | ^ + v | + Whoops Middleware + | ^ + v | + TrailingSlash + | ^ + v | + Routing + | ^ + v | + ContainerResolver + | ^ + v | + Controller/Action + +As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every +middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. + +```php +interface Pipeline +{ + public function dispatch(ServerRequestInterface $request): ResponseInterface; +} +``` + +And our implementation looks something like this: + +```php + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} +``` + +Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code +that should produce our response. + +In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument +and store that itself as the current tip. + +There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) + +Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline +Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline + +```php +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} +``` + +And configure the container to use the Factory to create the Pipeline: + +```php + ..., + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + ... +``` +And of course a new file called middlewares.php in our config folder: +```php +pipeline->dispatch($request); +} +``` + +Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our +pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a +dependency container, if you want you can do that as well. + +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) diff --git a/implementation/15-adding-content/phpstan-baseline.neon b/implementation/15-adding-content/phpstan-baseline.neon new file mode 100644 index 0000000..61697a1 --- /dev/null +++ b/implementation/15-adding-content/phpstan-baseline.neon @@ -0,0 +1,7 @@ +parameters: + ignoreErrors: + - + message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" + count: 1 + path: src/Http/InvokerRoutedHandler.php + diff --git a/implementation/15-adding-content/phpstan.neon b/implementation/15-adding-content/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/15-adding-content/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/15-adding-content/public/css/spectre-exp.min.css b/implementation/15-adding-content/public/css/spectre-exp.min.css new file mode 100644 index 0000000..d313774 --- /dev/null +++ b/implementation/15-adding-content/public/css/spectre-exp.min.css @@ -0,0 +1 @@ +/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/implementation/15-adding-content/public/css/spectre-icons.min.css b/implementation/15-adding-content/public/css/spectre-icons.min.css new file mode 100644 index 0000000..0276f7b --- /dev/null +++ b/implementation/15-adding-content/public/css/spectre-icons.min.css @@ -0,0 +1 @@ +/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/implementation/15-adding-content/public/css/spectre.min.css b/implementation/15-adding-content/public/css/spectre.min.css new file mode 100644 index 0000000..0fe23d9 --- /dev/null +++ b/implementation/15-adding-content/public/css/spectre.min.css @@ -0,0 +1 @@ +/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/implementation/15-adding-content/public/favicon.ico b/implementation/15-adding-content/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/15-adding-content/public/index.php b/implementation/15-adding-content/public/index.php new file mode 100644 index 0000000..d93da3a --- /dev/null +++ b/implementation/15-adding-content/public/index.php @@ -0,0 +1,5 @@ +getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render('hello', $data); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/15-adding-content/src/Action/Other.php b/implementation/15-adding-content/src/Action/Other.php new file mode 100644 index 0000000..da9ceaf --- /dev/null +++ b/implementation/15-adding-content/src/Action/Other.php @@ -0,0 +1,16 @@ +parse('This *works* **too!**'); + $response->getBody()->write($html); + return $response->withStatus(200); + } +} diff --git a/implementation/15-adding-content/src/Action/Page.php b/implementation/15-adding-content/src/Action/Page.php new file mode 100644 index 0000000..6a3aad0 --- /dev/null +++ b/implementation/15-adding-content/src/Action/Page.php @@ -0,0 +1,80 @@ +getPages(), + fn (string $filename) => str_contains($filename, $page) + ) + )[0]; + $markdown = file_get_contents($page); + + // fix the next and previous buttons to work with our routing + $markdown = preg_replace('/\(\d\d-/m', '(', $markdown); + $markdown = str_replace('.md)', ')', $markdown); + + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + $data = [ + 'title' => substr($page, 3), + 'content' => $this->parser->parse($markdown), + ]; + $html = $this->renderer->render('page/show', $data); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map(function (string $page) { + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + return [ + 'id' => substr($page, 0, 2), + 'title' => substr($page, 3), + ]; + }, $this->getPages()); + $html = $this->renderer->render('page/list', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } + + /** + * @return string[] + */ + private function getPages(): array + { + $files = glob($this->pagesPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return $files; + } +} diff --git a/implementation/15-adding-content/src/Bootstrap.php b/implementation/15-adding-content/src/Bootstrap.php new file mode 100644 index 0000000..3abc2e5 --- /dev/null +++ b/implementation/15-adding-content/src/Bootstrap.php @@ -0,0 +1,40 @@ +getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); diff --git a/implementation/15-adding-content/src/Exception/InternalServerError.php b/implementation/15-adding-content/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/15-adding-content/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +factory::fromGlobals(); + } + + /** + * @param UriInterface|string $uri + * @param array $serverParams + */ + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} diff --git a/implementation/15-adding-content/src/Factory/DoctrineEm.php b/implementation/15-adding-content/src/Factory/DoctrineEm.php new file mode 100644 index 0000000..b0be39b --- /dev/null +++ b/implementation/15-adding-content/src/Factory/DoctrineEm.php @@ -0,0 +1,32 @@ +settings->doctrine['devMode']); + + $config->setMetadataDriverImpl( + new AttributeDriver( + $this->settings->doctrine['metadataDirs'] + ) + ); + + return EntityManager::create( + $this->settings->connection, + $config, + ); + } +} diff --git a/implementation/15-adding-content/src/Factory/FileSystemSettingsProvider.php b/implementation/15-adding-content/src/Factory/FileSystemSettingsProvider.php new file mode 100644 index 0000000..f071078 --- /dev/null +++ b/implementation/15-adding-content/src/Factory/FileSystemSettingsProvider.php @@ -0,0 +1,22 @@ +filePath; + assert($settings instanceof Settings); + return $settings; + } +} diff --git a/implementation/15-adding-content/src/Factory/PipelineProvider.php b/implementation/15-adding-content/src/Factory/PipelineProvider.php new file mode 100644 index 0000000..77738f8 --- /dev/null +++ b/implementation/15-adding-content/src/Factory/PipelineProvider.php @@ -0,0 +1,25 @@ +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} diff --git a/implementation/15-adding-content/src/Factory/RequestFactory.php b/implementation/15-adding-content/src/Factory/RequestFactory.php new file mode 100644 index 0000000..2b17abc --- /dev/null +++ b/implementation/15-adding-content/src/Factory/RequestFactory.php @@ -0,0 +1,11 @@ +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = $settings; + $builder->addDefinitions($dependencies); + // $builder->enableCompilation('/tmp'); + return $builder->build(); + } +} diff --git a/implementation/15-adding-content/src/Factory/SettingsProvider.php b/implementation/15-adding-content/src/Factory/SettingsProvider.php new file mode 100644 index 0000000..ce1c5f0 --- /dev/null +++ b/implementation/15-adding-content/src/Factory/SettingsProvider.php @@ -0,0 +1,10 @@ +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + + echo $response->getBody(); + } +} diff --git a/implementation/15-adding-content/src/Http/ContainerPipeline.php b/implementation/15-adding-content/src/Http/ContainerPipeline.php new file mode 100644 index 0000000..816cedd --- /dev/null +++ b/implementation/15-adding-content/src/Http/ContainerPipeline.php @@ -0,0 +1,82 @@ + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} diff --git a/implementation/15-adding-content/src/Http/Emitter.php b/implementation/15-adding-content/src/Http/Emitter.php new file mode 100644 index 0000000..ce4c035 --- /dev/null +++ b/implementation/15-adding-content/src/Http/Emitter.php @@ -0,0 +1,10 @@ +getAttribute($this->routeAttributeName, false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} diff --git a/implementation/15-adding-content/src/Http/Pipeline.php b/implementation/15-adding-content/src/Http/Pipeline.php new file mode 100644 index 0000000..1a9dcda --- /dev/null +++ b/implementation/15-adding-content/src/Http/Pipeline.php @@ -0,0 +1,11 @@ +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} diff --git a/implementation/15-adding-content/src/Http/RoutedRequestHandler.php b/implementation/15-adding-content/src/Http/RoutedRequestHandler.php new file mode 100644 index 0000000..a7407c9 --- /dev/null +++ b/implementation/15-adding-content/src/Http/RoutedRequestHandler.php @@ -0,0 +1,10 @@ +pipeline->dispatch($request); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} diff --git a/app/src/Middleware/CacheMiddleware.php b/implementation/15-adding-content/src/Middleware/CacheMiddleware.php similarity index 100% rename from app/src/Middleware/CacheMiddleware.php rename to implementation/15-adding-content/src/Middleware/CacheMiddleware.php diff --git a/implementation/15-adding-content/src/Model/MarkdownPage.php b/implementation/15-adding-content/src/Model/MarkdownPage.php new file mode 100644 index 0000000..bae383c --- /dev/null +++ b/implementation/15-adding-content/src/Model/MarkdownPage.php @@ -0,0 +1,21 @@ +environment === 'dev'; + } +} diff --git a/app/src/Template/GithubMarkdownRenderer.php b/implementation/15-adding-content/src/Template/GithubMarkdownRenderer.php similarity index 100% rename from app/src/Template/GithubMarkdownRenderer.php rename to implementation/15-adding-content/src/Template/GithubMarkdownRenderer.php diff --git a/implementation/15-adding-content/src/Template/MarkdownParser.php b/implementation/15-adding-content/src/Template/MarkdownParser.php new file mode 100644 index 0000000..d404005 --- /dev/null +++ b/implementation/15-adding-content/src/Template/MarkdownParser.php @@ -0,0 +1,8 @@ +engine->render($template, $data); + } +} diff --git a/implementation/15-adding-content/src/Template/ParsedownParser.php b/implementation/15-adding-content/src/Template/ParsedownParser.php new file mode 100644 index 0000000..2ffd287 --- /dev/null +++ b/implementation/15-adding-content/src/Template/ParsedownParser.php @@ -0,0 +1,17 @@ +parser->parse($markdown); + } +} diff --git a/implementation/15-adding-content/src/Template/Renderer.php b/implementation/15-adding-content/src/Template/Renderer.php new file mode 100644 index 0000000..ff916ed --- /dev/null +++ b/implementation/15-adding-content/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data = []): string; +} diff --git a/implementation/15-adding-content/templates/hello.html b/implementation/15-adding-content/templates/hello.html new file mode 100644 index 0000000..15a4cd2 --- /dev/null +++ b/implementation/15-adding-content/templates/hello.html @@ -0,0 +1,6 @@ +{{> partials/head }} +
+

Hello {{name}}

+

The time is {{now}}

+
+{{> partials/foot }} diff --git a/implementation/15-adding-content/templates/page.html b/implementation/15-adding-content/templates/page.html new file mode 100644 index 0000000..c3c5284 --- /dev/null +++ b/implementation/15-adding-content/templates/page.html @@ -0,0 +1,5 @@ +{{> partials/head }} +
+ {{{content}}} +
+{{> partials/foot }} diff --git a/implementation/15-adding-content/templates/page/list.html b/implementation/15-adding-content/templates/page/list.html new file mode 100644 index 0000000..bf42348 --- /dev/null +++ b/implementation/15-adding-content/templates/page/list.html @@ -0,0 +1,19 @@ + + + + + Pages + + + +
+ +
+ + \ No newline at end of file diff --git a/implementation/15-adding-content/templates/page/show.html b/implementation/15-adding-content/templates/page/show.html new file mode 100644 index 0000000..abe295e --- /dev/null +++ b/implementation/15-adding-content/templates/page/show.html @@ -0,0 +1,17 @@ + + + + + {{title}} + + + + + + +
+ {{{content}}} +
+ + \ No newline at end of file diff --git a/implementation/15-adding-content/templates/pagelist.html b/implementation/15-adding-content/templates/pagelist.html new file mode 100644 index 0000000..538e2c4 --- /dev/null +++ b/implementation/15-adding-content/templates/pagelist.html @@ -0,0 +1,11 @@ +{{> partials/head }} +
+ +
+{{> partials/foot }} diff --git a/implementation/15-adding-content/templates/partials/foot.html b/implementation/15-adding-content/templates/partials/foot.html new file mode 100644 index 0000000..17c7245 --- /dev/null +++ b/implementation/15-adding-content/templates/partials/foot.html @@ -0,0 +1,3 @@ +
+ + \ No newline at end of file diff --git a/implementation/15-adding-content/templates/partials/head.html b/implementation/15-adding-content/templates/partials/head.html new file mode 100644 index 0000000..421d387 --- /dev/null +++ b/implementation/15-adding-content/templates/partials/head.html @@ -0,0 +1,11 @@ + + + + + No Framework: {{title}} + + + + + +
diff --git a/implementation/16-data-repository/.php-cs-fixer.php b/implementation/16-data-repository/.php-cs-fixer.php new file mode 100644 index 0000000..705a7d7 --- /dev/null +++ b/implementation/16-data-repository/.php-cs-fixer.php @@ -0,0 +1,38 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config' + ]) + ); \ No newline at end of file diff --git a/implementation/16-data-repository/.phpcs.xml.dist b/implementation/16-data-repository/.phpcs.xml.dist new file mode 100644 index 0000000..3b433f6 --- /dev/null +++ b/implementation/16-data-repository/.phpcs.xml.dist @@ -0,0 +1,9 @@ + + + + + src + config + + + \ No newline at end of file diff --git a/implementation/16-data-repository/cli-config.php b/implementation/16-data-repository/cli-config.php new file mode 100644 index 0000000..fbc6598 --- /dev/null +++ b/implementation/16-data-repository/cli-config.php @@ -0,0 +1,13 @@ +getContainer(); + +return ConsoleRunner::createHelperSet($container->get(EntityManagerInterface::class)); diff --git a/implementation/16-data-repository/composer.json b/implementation/16-data-repository/composer.json new file mode 100644 index 0000000..b5c7f1a --- /dev/null +++ b/implementation/16-data-repository/composer.json @@ -0,0 +1,54 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3", + "mustache/mustache": "^2.14", + "psr/http-server-middleware": "^1.0", + "middlewares/trailing-slash": "^2.0", + "middlewares/whoops": "^2.0", + "erusev/parsedown": "^1.7", + "league/commonmark": "^2.2" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require-dev": { + "phpstan/phpstan": "^1.5", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0", + "squizlabs/php_codesniffer": "^3.6", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.1", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "mnapoli/hard-mode": "^0.3.0" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1234 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + } +} diff --git a/implementation/16-data-repository/composer.lock b/implementation/16-data-repository/composer.lock new file mode 100644 index 0000000..a62d9c7 --- /dev/null +++ b/implementation/16-data-repository/composer.lock @@ -0,0 +1,2438 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "00acf07ae222f9117a84bce157b99837", + "packages": [ + { + "name": "dflydev/dot-access-data", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^3.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" + }, + "time": "2021-08-13T13:06:58+00:00" + }, + { + "name": "erusev/parsedown", + "version": "1.7.4", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "support": { + "issues": "https://github.com/erusev/parsedown/issues", + "source": "https://github.com/erusev/parsedown/tree/1.7.x" + }, + "time": "2019-12-30T22:54:17+00:00" + }, + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-03-29T20:12:16+00:00" + }, + { + "name": "league/commonmark", + "version": "2.2.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", + "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.15" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.30.0", + "commonmark/commonmark.js": "0.30.0", + "composer/package-versions-deprecated": "^1.8", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4", + "phpstan/phpstan": "^0.12.88 || ^1.0.0", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2022-02-26T21:24:45+00:00" + }, + { + "name": "league/config", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.90", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2021-08-14T12:15:32+00:00" + }, + { + "name": "middlewares/trailing-slash", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/middlewares/trailing-slash.git", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", + "shasum": "" + }, + "require": { + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to normalize the trailing slash of the uri path", + "homepage": "https://github.com/middlewares/trailing-slash", + "keywords": [ + "http", + "middleware", + "normalize", + "path", + "psr-15", + "psr-7", + "slash" + ], + "support": { + "issues": "https://github.com/middlewares/trailing-slash/issues", + "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" + }, + "time": "2020-12-02T00:06:55+00:00" + }, + { + "name": "middlewares/utils", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/middlewares/utils.git", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v2.16", + "guzzlehttp/psr7": "^2.0", + "laminas/laminas-diactoros": "^2.4", + "nyholm/psr7": "^1.0", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "slim/psr7": "^1.4", + "squizlabs/php_codesniffer": "^3.5", + "sunrise/http-message": "^1.0", + "sunrise/http-server-request": "^1.0", + "sunrise/stream": "^1.0.15", + "sunrise/uri": "^1.0.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\Utils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Common utils for PSR-15 middleware packages", + "homepage": "https://github.com/middlewares/utils", + "keywords": [ + "PSR-11", + "http", + "middleware", + "psr-15", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/middlewares/utils/issues", + "source": "https://github.com/middlewares/utils/tree/v3.3.0" + }, + "time": "2021-07-04T17:56:23+00:00" + }, + { + "name": "middlewares/whoops", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/middlewares/whoops.git", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.5", + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^5.0 || ^7.0", + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to use Whoops as error handler", + "homepage": "https://github.com/middlewares/whoops", + "keywords": [ + "error", + "http", + "middleware", + "psr-15", + "psr-7", + "server", + "whoops" + ], + "support": { + "issues": "https://github.com/middlewares/whoops/issues", + "source": "https://github.com/middlewares/whoops/tree/v2.0.2" + }, + "time": "2022-01-27T20:31:30+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nette/schema", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", + "shasum": "" + }, + "require": { + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": ">=7.1 <8.2" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "phpstan/phpstan-nette": "^0.12", + "tracy/tracy": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.2.2" + }, + "time": "2021-10-15T11:40:02+00:00" + }, + { + "name": "nette/utils", + "version": "v3.2.7", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", + "shasum": "" + }, + "require": { + "php": ">=7.2 <8.2" + }, + "conflict": { + "nette/di": "<3.0.6" + }, + "require-dev": { + "nette/tester": "~2.0", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v3.2.7" + }, + "time": "2022-01-24T11:29:14+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/master" + }, + "time": "2018-10-30T17:12:04+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "shasum": "" + }, + "require": { + "php": ">=8.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:55:41+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-04T08:16:47+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "doctrine/coding-standard", + "version": "8.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/coding-standard.git", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Müller", + "email": "st.mueller@dzh-online.de" + } + ], + "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "doctrine", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/doctrine/coding-standard/issues", + "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" + }, + "time": "2021-04-03T10:54:55+00:00" + }, + { + "name": "mnapoli/hard-mode", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/hard-mode.git", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", + "shasum": "" + }, + "require": { + "doctrine/coding-standard": "^8.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Hard mode for PHP", + "support": { + "issues": "https://github.com/mnapoli/hard-mode/issues", + "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" + }, + "time": "2020-10-12T07:54:37+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.5.4", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", + "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.5.4" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-04-03T12:39:00+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^1.2.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" + }, + "time": "2021-11-18T09:30:29+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/16-data-repository/config/dependencies.php b/implementation/16-data-repository/config/dependencies.php new file mode 100644 index 0000000..0040933 --- /dev/null +++ b/implementation/16-data-repository/config/dependencies.php @@ -0,0 +1,55 @@ + fn (SystemClockNow $n) => $n, + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, + Emitter::class => fn (BasicEmitter $e) => $e, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, + CacheInterface::class => fn (FilesystemAdapter $a) => $a, + MarkdownParser::class => fn (ParsedownParser $p) => $p, + MarkdownPageRepo::class => fn (FileSystemMarkdownPageRepo $r) => $r, + + // Factories + ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), + Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), +]; diff --git a/implementation/16-data-repository/config/middlewares.php b/implementation/16-data-repository/config/middlewares.php new file mode 100644 index 0000000..71dd461 --- /dev/null +++ b/implementation/16-data-repository/config/middlewares.php @@ -0,0 +1,11 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/page', [Page::class, 'list']); + $r->addRoute('GET', '/page/{page}', [Page::class, 'show']); + $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); + $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/implementation/16-data-repository/config/settings.php b/implementation/16-data-repository/config/settings.php new file mode 100644 index 0000000..c654565 --- /dev/null +++ b/implementation/16-data-repository/config/settings.php @@ -0,0 +1,12 @@ +>](02-composer.md) + +### Front Controller + +A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. + +To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. + +A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. + +The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. + +But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. + +So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. + +Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: + +```php +>](02-composer.md) diff --git a/implementation/16-data-repository/data/pages/02-composer.md b/implementation/16-data-repository/data/pages/02-composer.md new file mode 100644 index 0000000..a25a4a8 --- /dev/null +++ b/implementation/16-data-repository/data/pages/02-composer.md @@ -0,0 +1,75 @@ +[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) + +### Composer + +[Composer](https://getcomposer.org/) is a dependency manager for PHP. + +Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do +something. With Composer, you can install third-party libraries for your application. + +If you don't have Composer installed already, head over to the website and install it. You can find Composer packages +for your project on [Packagist](https://packagist.org/). + +Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will +be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. + +Add the following content to the file: + +```json +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubiana", + "email": "lubiana@hannover.ccc.de" + } + ] +} +``` + +In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use +whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. +Just replace it with your namespace in your own code. + +I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. + +As the Bootstrap.php file is placed in that directory we should +add the namespace to the File as well. Here is my current Bootstrap.php +as a reference: + +```php +>](03-error-handler.md) diff --git a/implementation/16-data-repository/data/pages/03-error-handler.md b/implementation/16-data-repository/data/pages/03-error-handler.md new file mode 100644 index 0000000..60465d0 --- /dev/null +++ b/implementation/16-data-repository/data/pages/03-error-handler.md @@ -0,0 +1,79 @@ +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) + +### Error Handler + +An error handler allows you to customize what happens if your code results in an error. + +A nice error page with a lot of information for debugging goes a long way during development. So the first package +for your application will take care of that. + +I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. +If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, +you have total control over your project. + +An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) + +To install a new package, open up your `composer.json` and add the package to the require part. It should now look +like this: + +```php +"require": { + "php": ">=8.1.0", + "filp/whoops": "^2.14" +}, +``` + +Now run `composer update` in your console and it will be installed. + +Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, +i that case composer automatically installs the package and updates your composer.json-file. + +But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, +ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you +only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. + +**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message +can help someone to gain access to your system. Always show a user friendly error page instead and send an email to +yourself, write to a log or something similar. So only you can see the errors in the production environment. + +For development that does not make sense though -- you want a nice error page. The solution is to have an environment +switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in +case no environment has been set. + +Then after the error handler registration, throw an `Exception` to test if everything is working correctly. +Your `Bootstrap.php` should now look similar to this: + +```php +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (\Throwable $e) { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +throw new \Exception("Ooooopsie"); + +``` + +You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until +you get it working. Now would also be a good time for another commit. + + +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/implementation/16-data-repository/data/pages/04-development-helpers.md b/implementation/16-data-repository/data/pages/04-development-helpers.md new file mode 100644 index 0000000..9505284 --- /dev/null +++ b/implementation/16-data-repository/data/pages/04-development-helpers.md @@ -0,0 +1,260 @@ +[<< previous](03-error-handler.md) | [next >>](05-http.md) + +### Development Helpers + +I have added some more helpers to my composer.json that help me with development. As these are scripts and programms +used only for development they should not be used in a production environment. Composer has a specific sections in its +file called "dev-dependencies", everything that is required in this section does not get installen in production. + +Let's install our dev-helpers and i will explain them one by one: +`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` + +#### Static Code Analysis with phpstan + +Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or +create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements +that are not (or not always) available, if-statements that always are true and a lot of other stuff. + +A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. + +```php +/** + * @param \DateTime $date + * @return void + */ +function printDate($date) { + $date->format('Y-m-d H:i:s'); +} + +printDate('now'); +``` +if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` + +It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has +no use, and secondly, that we are calling the function with a string instead of a datetime object. + +```shell +1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + ------ --------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ --------------------------------------------------------------------------------------------- +30 Call to method DateTime::format() on a separate line has no effect. +33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. + ------ --------------------------------------------------------------------------------------------- +``` + +The second error is something that "declare strict-types" already catches for us, but the first error is something that +we usually would not discover easily without speccially looking for this errortype. + +We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and +path everytime we want to check our code for errors: + +```yaml +parameters: + level: max + paths: + - src +``` +now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project + +With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us +some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. + +```shell +composer require --dev phpstan/extension-installer +composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules +``` + +During the first install you need to allow the extension installer to actually install the extension. The second command +installs some more strict rulesets and activates them in phpstan. + +If we now rerun phpstan it already tells us about some errors we have made: + +``` + ------ ----------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ ----------------------------------------------------------------------------------------------- +10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider + using long ternary. +25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: + http://bit.ly/subtypeexception +26 Unreachable statement - code above always terminates. + ------ ----------------------------------------------------------------------------------------------- +``` + +The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove +that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific +problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working +on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages +everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we +are adding to our code. + +In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the +phpstan.neon file and run phpstan with the +'--generate-baseline' option: + +```yaml +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` +```shell +[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline +Note: Using configuration file /home/vagrant/app/phpstan.neon. + 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + + + [OK] Baseline generated with 1 error. + + +``` + +you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) + +#### PHP-CS-Fixer + +Another great tool is the php-cs-fixer, which just applies a specific style to your code. + +when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current +directory. + +You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) + +personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup +a configuration file that i use in all my projects .php-cs-fixer.php: + +```php +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + ]) + ); +``` + +#### PHP Codesniffer + +The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra +rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. + +it provides the `phpcs` command to check for violations and the `phpcbf` command to actually fix most of the violations. + +Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have +guessed we are adding some extra rules. + +Lets install the ruleset with composer +```shell +composer require --dev mnapoli/hard-mode +``` + +and add a configuration file to actually use it '.phpcs.xml.dist' +```xml + + + + + src + + + +``` + +running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. + +``` +[vagrant@archlinux app]$ ./vendor/bin/phpcs + +FILE: src/Bootstrap.php +---------------------------------------------------------------------------------------------------- +FOUND 4 ERRORS AFFECTING 4 LINES +---------------------------------------------------------------------------------------------------- + 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. + 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead +---------------------------------------------------------------------------------------------------- +PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY +---------------------------------------------------------------------------------------------------- + +Time: 639ms; Memory: 10MB +``` + +You can then use `./vendor/bin/phpcbf` to try to fix them. + + +#### Symfony Var-Dumper + +another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small +functions. + +dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects +or arrays. + +dd() on the other hand is a function that dumps its parameters and then exits the php-script. + +you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. + +#### Composer scripts + +now we have a few commands that are available on the command line. i personally do not like to type complex commands +with lots of parameters by hand all the time, so i added a few lines to my composer.json: + +```json +"scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" +}, +``` + +that way i can just type "composer" followed by the command name in the root of my project. if i want to start the +php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the +time. + +You could also configure PhpStorm to automatically run these commands in the background and highlight the violations +directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my +flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. + +My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before +commiting and pushing the code. + +[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/implementation/16-data-repository/data/pages/05-http.md b/implementation/16-data-repository/data/pages/05-http.md new file mode 100644 index 0000000..6166214 --- /dev/null +++ b/implementation/16-data-repository/data/pages/05-http.md @@ -0,0 +1,124 @@ +[<< previous](04-development-helpers.md) | [next >>](06-router.md) + +### HTTP + +PHP already has a few things built in to make working with HTTP easier. For example there are the +[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. + +These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, +if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, +then you will want a class with a nice object-oriented interface that you can use in your application instead. + +Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The +standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php +projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. + +As this is a widely adopted standard there are already several implementations available for us to use. I will choose +the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. + +Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a +[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. + +Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use +that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding +the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). + + +to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit +enter + +Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): + +```php +$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); +$response = new \Laminas\Diactoros\Response; +$response->getBody()->write('Hello World! '); +$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); +``` + +This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. + +In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the +write()-Method on that object. + + +To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: + +```php +echo $response->getBody(); +``` + +This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only +stores data. + +You can play around with the other methods of the Request object and take a look at its content with the dd() function. + +```php +dd($response) +``` + +Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot +be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which +creates a copy of the request object with the changed property and returns that clone: + +```php +$response = $response->withStatus(200); +$response = $response->withAddedHeader('Content-type', 'application/json'); +``` + +If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been +defined this way. + +But if you have been keeping attention you might argue that the following line should not work if the request object is +immutable. + +```php +$response->getBody()->write('Hello World!'); +``` + +The response-body implements a stream interface which is immutable for some reasons that are described in the +[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be +aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in +the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. +I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content +to a response object. + +```php +$body = $response->getBody(); +$body->write('Hello World!'); +$response = $response->withBody($body); +``` + +Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our +output-logic a little bit more. Replace the line that echos the response-body with the following: + +```php +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); +``` + +This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a +webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. + +Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. + +Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter + + +[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/implementation/16-data-repository/data/pages/06-router.md b/implementation/16-data-repository/data/pages/06-router.md new file mode 100644 index 0000000..6c39ae5 --- /dev/null +++ b/implementation/16-data-repository/data/pages/06-router.md @@ -0,0 +1,101 @@ +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) + +### Router + +A router dispatches to different handlers depending on rules that you have set up. + +With your current setup it does not matter what URL is used to access the application, it will always result in the same +response. So let's fix that now. + +I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own +favorite package. + +Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) + +By now you know how to install Composer packages, so I will leave that to you. + +Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. + +```php +$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +switch ($routeInfo[0]) { + case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: + $response = (new \Laminas\Diactoros\Response)->withStatus(405); + $response->getBody()->write('Method not allowed'); + $response = $response->withStatus(405); + break; + case \FastRoute\Dispatcher::FOUND: + $handler = $routeInfo[1]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + /** @var \Psr\Http\Message\ResponseInterface $response */ + $response = call_user_func($handler, $request); + break; + case \FastRoute\Dispatcher::NOT_FOUND: + default: + $response = (new \Laminas\Diactoros\Response)->withStatus(404); + $response->getBody()->write('Not Found!'); + break; +} +``` + +In the first part of the code, you are registering the available routes for your application. In the second part, the +dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, +we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. +If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. + +This setup might work for really small applications, but once you start adding a few routes your bootstrap file will +quickly get cluttered. So let's move them out into a separate file. + +Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; + +```php +addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}; +``` + +Now let's rewrite the route dispatcher part to use the `Routes.php` file. + +```php +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); +``` + +This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. + +Of course we now need to add the 'config' folder to the configuration files of our +devhelpers so that they can scan that directory as well. + +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/implementation/16-data-repository/data/pages/07-dispatching-to-a-class.md b/implementation/16-data-repository/data/pages/07-dispatching-to-a-class.md new file mode 100644 index 0000000..0c961a4 --- /dev/null +++ b/implementation/16-data-repository/data/pages/07-dispatching-to-a-class.md @@ -0,0 +1,137 @@ +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) + +### Dispatching to a Class + +In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). +MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to +learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) +and the followup posts. + +So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). + +We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other +common names are 'Controllers' or 'Actions'. + +Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. +In there, create a `Hello.php` file. + +```php +getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + } +} +``` + +You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) +that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is +fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement +it in some other parts of our application as well. In order to use that Interface we have to require it with composer: +'composer require psr/http-server-handler'. + +The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. +At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. + +Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: + +```php +return function(\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); + $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); +}; +``` + +Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared +the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing +the response to the one we defined for the second route. + +To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: + +```php +case \FastRoute\Dispatcher::FOUND: + $handler = new $routeInfo[1]; + if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { + throw new \Exception('Invalid Requesthandler'); + } + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + assert($response instanceof \Psr\Http\Message\ResponseInterface) + break; +``` + +So instead of just calling a method you are now instantiating an object and then calling the method on it. + +Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. + +And of course don't forget to commit your changes. + +Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still +generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for +example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still +have our whoopsie error-handler but i like to be more explicit in my control flow. + +In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new +Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and +InternalServerError. All three should extend phps Base Exception class. + +Here is my NotFound.php for example. + +```php + $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody()->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} +``` + +Check if our code still works, try to trigger some errors, run phpstan and the fix command +and don't forget to commit your changes. + +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/implementation/16-data-repository/data/pages/08-inversion-of-control.md b/implementation/16-data-repository/data/pages/08-inversion-of-control.md new file mode 100644 index 0000000..21f4f23 --- /dev/null +++ b/implementation/16-data-repository/data/pages/08-inversion-of-control.md @@ -0,0 +1,54 @@ +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) + +### Inversion of Control + +In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we +want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then +we would need to edit every one of our request handlers to call a different constructor of the class. + +The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that +instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done +with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). + +If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is +implemented, it will make sense. + +Change your `Hello` action to the following: + +```php +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: + +```php +$handler = new $className($response); +``` + +Of course we need to also update all the other handlers. + +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/implementation/16-data-repository/data/pages/09-dependency-injector.md b/implementation/16-data-repository/data/pages/09-dependency-injector.md new file mode 100644 index 0000000..7f7c6a2 --- /dev/null +++ b/implementation/16-data-repository/data/pages/09-dependency-injector.md @@ -0,0 +1,213 @@ +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) + +### Dependency Injector + +A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when +the class is instantiated. + +Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work +with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look +for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). + +I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) +out of the box. + +After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: + +```php +addDefinitions([ + \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), +]); + +return $builder->build(); +``` + +In this file we create a containerbuilder, add some definitions to it and return the container. +As the container supports autowiring we only need to define services where we want to use a specific implementation of +an interface. + +In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to +tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second +exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. + +Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), +as we will use that extensively. + +Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally +to create our Handler-Object + +```php +$container = require __DIR__ . '/../config/dependencies.php'; +assert($container instanceof \Psr\Container\ContainerInterface); + +$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); +assert($request instanceof \Psr\Http\Message\ServerRequestInterface); +``` + +The other part that has to be changed is the dispatching of the route. Before you had the following code: + +```php +$className = $routeInfo[1]; +$handler = new $className($response); +assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Change that to the following: + +```php +/** @var RequestHandlerInterface $handler */ +$className = $routeInfo[1]; +$handler = $container->get($className); +assert($handler instanceof RequestHandlerInterface); +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Make sure to use the container fetch the response object in the catch blocks as well: + +```php +} catch (MethodNotAllowed) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(404); + $response->getBody()->write('Not Found'); +} +``` + +Now all your controller constructor dependencies will be automatically resolved with PHP-DI. + +We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons +in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call +`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we +want to work with a different date in a test. + +Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation +that creates us a DateTimeImmutable object with the current date and time. + +src/Service/Time/Now.php: +```php +namespace Lubian\NoFramework\Service\Time; + +interface Now +{ + public function __invoke(): \DateTimeImmutable; +} +``` +src/Service/Time/SystemClockNow.php: +```php +namespace Lubian\NoFramework\Service\Time; + +final class SystemClockNow implements Now +{ + + public function __invoke(): \DateTimeImmutable + { + return new \DateTimeImmutable; + } +} +``` +If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and +update the handle-method to use the new class property: + +```php +getAttribute('name', 'Stranger'); + $nowAsString = ($this->now)()->format('H:i:s'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowAsString); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI +automatically figures out what classes are requested in the constructor and tries to create the objects needed. + +But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred +S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: + +```php + public function __construct( + private ResponseInterface $response, + private Now $now, + ) +``` + +When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation +should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: + +```php +\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), +``` + +we could also use the PHP-DI create method to delegate the object creation to the container implementation: +```php +\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), +``` + +this way the container can try to resolve any dependencies that the class might have internally, but prefer the other +method because we are not depending on this specific dependency injection implementation. + +Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are +requesting the Hello action. + +If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As +we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way +we wont get any warnings for this particular issue, but for any other issues we add to our code. + +Update the phpstan.neon file to include a "baseline" file: + +``` +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` + +if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and +ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just +'baseline' + +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/implementation/16-data-repository/data/pages/10-invoker.md b/implementation/16-data-repository/data/pages/10-invoker.md new file mode 100644 index 0000000..3033fae --- /dev/null +++ b/implementation/16-data-repository/data/pages/10-invoker.md @@ -0,0 +1,102 @@ +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) + +### Invoker + +Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the +one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long +with some services and not the whole Requestobject with all its various properties. + +If we take our Hello action for example we only need a response object, the time service and the 'name' information from +the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named +the class hello and it would be redundant to also call the the method hello. So an updated version of that class could +look like this: + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + string $name = 'Stranger', + ): ResponseInterface + { + $body = $this->response->getBody(); + $nowString = $now->get()->format('H:i:s'); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowString); + return $response + ->withBody($body) + ->withStatus(200); + } +} +``` + +It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short +closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the +rootpath of our application yet. + +```php +$r->addRoute('GET', '/hello[/{name}]', Hello::class); +$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); +$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +``` + +In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the +route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that +class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what +arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router +if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple +callable we would need to manually use reflection as well to resolve all the arguments. + +But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) +which handles all of that for us. + +After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo +switch section in the Bootstrap.php file to use the container->call() method: + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2]; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$response = $container->call($handler, $args); +``` + +Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. + +But by now you should know that I do not like to depend on specific implementations and the call method is not defined in +the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container +or any other implementation. + +Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific +container implementation so we could use it with any container that implements the ContainerInterface. And best of all +the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that +we could implement if we ever want to write our own implementation or we could write an adapter that uses a different +class that solves the same problem. + +But for now we are using the solution provided by PHP-DI. +So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2] ?? []; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$invoker = $container->get(InvokerInterface::class); +assert($invoker instanceof InvokerInterface); +$response = $invoker->call($handler, $args); +assert($response instanceof ResponseInterface); +``` + +Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) +by php, and even some more. + +But let us move on to something more fun and add some templating functionality to our application as we are trying to build +a website in the end. + +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/implementation/16-data-repository/data/pages/11-templating.md b/implementation/16-data-repository/data/pages/11-templating.md new file mode 100644 index 0000000..7bfe1aa --- /dev/null +++ b/implementation/16-data-repository/data/pages/11-templating.md @@ -0,0 +1,236 @@ +[<< previous](10-invoker.md) | [next >>](12-configuration.md) + +### Templating + +A template engine is not necessary with PHP because the language itself can take care of that. But it can make things +like escaping values easier. They also make it easier to draw a clear line between your application logic and the +template files which should only put your variables into the HTML code. + +A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please +also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. +Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. + +For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install +that package before you continue (`composer require mustache/mustache`). + +Another well known alternative would be [Twig](http://twig.sensiolabs.org/). + +Now please go and have a look at the source code of the +[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class +does not implement an interface. + +You could just type hint against the concrete class. But the problem with this approach is that you create tight +coupling. + +In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the +implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want +to add functionality to the engine. You can't do that without going back and changing all your code that is tightly +coupled. + +What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need +another implementation, you just implement that interface in your new class and inject the new class instead. + +Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). +This sounds a lot more complicated than it is, so just follow along. + +First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). +This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. +A class can implement multiple interfaces if necessary. + +So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a +new folder in your `src/` folder with the name `Template` where you can put all the template related things. + +In there create a new interface `Renderer.php` that looks like this: + +```php + $data */ + public function render(string $template, array $data = []) : string; +} +``` + +Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file +`MustacheRenderer.php` with the following content: + +```php +engine->render($template, $data); + } +} +``` + +As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple +and only fulfills the interface. + +Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know +which implementation he has to inject when you hint for the interface. Add this line: + +```php +[ + ... + \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) + ->constructor(new Mustache_Engine), +] +``` + +Now update the Hello.php class to require an implementation of our renderer interface +and use that to render a string using mustache syntax. + + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + Renderer $renderer, + string $name = 'Stranger', + ): ResponseInterface { + $body = $response->getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render( + 'Hello {{name}}, the time is {{now}}!', + $data, + ); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} +``` + +Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. +But what we want is template files, so let's go back and change that. + +To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the +`dependencies.php` file and add the following code: + +```php +[ + ... + Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), + Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), +] +``` + +We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. +Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have +to rename all the template files. + +To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the +dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader +in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object +we can then use it in the factory. + +In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: + +``` +

Hello World

+Hello {{ name }} +``` + +Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` + +Navigate to the hello page in your browser to make sure everything works. + +One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies +file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration +values. + +Lets create a 'Settings' class in our './src' Folder: + +```php +addDefinitions([ + Settings::class => fn () => require __DIR__ '/settings.php', + ResponseInterface::class => create(Response::class), + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + Renderer::class => fn (ME $me) => new Mustache($me), + MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), +]); + +return $builder->build(); +``` + + + +And as always, don't forget to commit your changes. + + +[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/implementation/16-data-repository/data/pages/12-configuration.md b/implementation/16-data-repository/data/pages/12-configuration.md new file mode 100644 index 0000000..a44dfd5 --- /dev/null +++ b/implementation/16-data-repository/data/pages/12-configuration.md @@ -0,0 +1,201 @@ +[<< previous](11-templating.md) | [next >>](13-refactoring.md) + +### Configuration + +In the last chapter we added some more definitions to our dependencies.php in that definitions +we needed to pass quite a few configuration settings and filesystem strings to the constructors +of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. + +As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. + +As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. + +Lets create a 'Settings' class in our './src' Folder: + +```php +filePath; + } +} +``` + +If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files +and craft a settings object from them. + +As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another +factory for our Container because everyone should have a Friend :) + +```php +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn () => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} +``` + +For this to work we need to change our dependencies.php file to just return the array of definitions: +And here we can instantly use the Settings object to create our template engine. + +```php + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), +]; +``` + +Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: +require __DIR__ . '/../vendor/autoload.php'; + +```php +... +error_reporting(E_ALL); + +$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); +$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); +... +``` + +Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. + +[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/implementation/16-data-repository/data/pages/13-refactoring.md b/implementation/16-data-repository/data/pages/13-refactoring.md new file mode 100644 index 0000000..067e168 --- /dev/null +++ b/implementation/16-data-repository/data/pages/13-refactoring.md @@ -0,0 +1,377 @@ +[<< previous](12-configuration.md) | [next >>](14-middleware.md) + +### Refactoring + +By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no +reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. +After all the bootstrap file should just set up the classes needed for the handling logic and execute them. + +At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. +As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the +method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non +static and extracted an interface. + +'./src/Http/Emitter.php' +```php +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + echo $response->getBody(); + } +} +``` +After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following +code in the Bootstrap.php to emit the response: + +```php +/** @var Emitter $emitter */ +$emitter = $container->get(Emitter::class); +$emitter->emit($response); +``` + +If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily +write an adapter that implements your emitter interface and wraps that more advanced emitter + +Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and +calling the routerhandler that in the passes the request to a function and gets the response. + +For this to steps to be seperated we are going to create two more classes: +1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object +2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the + requestobject, fetches the correct object from the container and calls it to create a response. + +Lets create the HandlerInterface first: + +```php +getAttribute($this->routeAttributeName, false); + assert($handler !== false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} + +``` + +We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. +The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler +as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in +the next chapter. + +```php +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} +``` + +Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. +```php +[ + '...', + Emitter::class => fn (BasicEmitter $e) => $e, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, +], +``` + +And then we can update our Bootstrap.php to fetch all the services and let them handle the request. + +```php +... +$routeMiddleWare = $container->get(MiddlewareInterface::class); +assert($routeMiddleWare instanceof MiddlewareInterface); +$handler = $container->get(RoutedRequestHandler::class); +assert($handler instanceof RequestHandlerInterface); +$emitter = $container->get(Emitter::class); +assert($emitter instanceof Emitter); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$response = $routeMiddleWare->process($request, $handler); +$emitter->emit($response); +``` +Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of +code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). + +So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. + +I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class +should require to function properly. + +* A RequestFactory + We want our Kernel to be able to build the request itself +* An Emitter + Without an Emitter we will not be able to send the response to the client +* RouteMiddleware + To decore the request with the correct handler for the requested route +* RequestHandler + To delegate the request to the correct funtion that creates the response + +As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface +to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our +interface: + +```php +factory::fromGlobals(); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} +``` + +For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: + +```php +routeMiddleware->process($request, $this->handler); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} + +``` + +We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines + +```php +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); +``` + +You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking +at the Whoops output and adding the needed definitions to the dependencies.php file. + +And as always, don't forget to commit your changes. + +[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/implementation/16-data-repository/data/pages/14-middleware.md b/implementation/16-data-repository/data/pages/14-middleware.md new file mode 100644 index 0000000..e698327 --- /dev/null +++ b/implementation/16-data-repository/data/pages/14-middleware.md @@ -0,0 +1,298 @@ +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) + +### Middleware + +In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain +a bit more about what this interface does and why it is used in many applications. + +The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets +passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all +the middlewars to the client/emitter. + +So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the +response after it gets created by our handlers. + +So lets take a look at the middleware and the requesthandler interfaces + +```php +interface MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; +} + +interface RequestHandlerInterface +{ + public function handle(ServerRequestInterface $request): ResponseInterface; +} +``` + +The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a +requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the +response. + +But the middleware could just ignore the handler and produce a response on its own as the interface just requires us +to produce a response. + +A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users +that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the +database. + +In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates +the request with an 'isAuthenticated' attribute. + +If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that +response is not already cached, than we let the handler create the response and store that in the cache for a few +seconds + +```php +interface CacheInterface +{ + public function get(string $key, callable $resolver, int $ttl): mixed; +} +``` + +The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one +defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses +the callable to produce the value and stores it for the time specified in ttl. + +so lets write our caching middleware: + +```php +final class CachingMiddleware implements MiddlewareInterface +{ + public function __construct(private CacheInterface $cache){} + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { + $key = $request->getUri()->getPath(); + return $this->cache->get($key, fn() => $handler->handle($request), 10); + } + return $handler->handle($request); + } +} +``` + +we can also modify the response after it has been created by our application, for example we could implement a gzip +middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser +know that our application is used to serve dank memes: + +```php +final class DankMemeMiddleware implements MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $response = $handler->handle($request); + return $response->withAddedHeader('Meme', 'Dank'); + } +} +``` + +but for our application we are going to just add two external middlewares: + +* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. +* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware + +```bash +composer require middlewares/trailing-slash +composer require middlewares/whoops +``` + +The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the +application as well as the middleware stack. + +Our desired request -> response flow looks something like this: + + Client + | ^ + v | + Kernel + | ^ + v | + Whoops Middleware + | ^ + v | + TrailingSlash + | ^ + v | + Routing + | ^ + v | + ContainerResolver + | ^ + v | + Controller/Action + +As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every +middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. + +```php +interface Pipeline +{ + public function dispatch(ServerRequestInterface $request): ResponseInterface; +} +``` + +And our implementation looks something like this: + +```php + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} +``` + +Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code +that should produce our response. + +In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument +and store that itself as the current tip. + +There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) + +Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline +Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline + +```php +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} +``` + +And configure the container to use the Factory to create the Pipeline: + +```php + ..., + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + ... +``` +And of course a new file called middlewares.php in our config folder: +```php +pipeline->dispatch($request); +} +``` + +Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our +pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a +dependency container, if you want you can do that as well. + +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) diff --git a/implementation/16-data-repository/phpstan-baseline.neon b/implementation/16-data-repository/phpstan-baseline.neon new file mode 100644 index 0000000..61697a1 --- /dev/null +++ b/implementation/16-data-repository/phpstan-baseline.neon @@ -0,0 +1,7 @@ +parameters: + ignoreErrors: + - + message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" + count: 1 + path: src/Http/InvokerRoutedHandler.php + diff --git a/implementation/16-data-repository/phpstan.neon b/implementation/16-data-repository/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/16-data-repository/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/16-data-repository/public/css/spectre-exp.min.css b/implementation/16-data-repository/public/css/spectre-exp.min.css new file mode 100644 index 0000000..d313774 --- /dev/null +++ b/implementation/16-data-repository/public/css/spectre-exp.min.css @@ -0,0 +1 @@ +/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/implementation/16-data-repository/public/css/spectre-icons.min.css b/implementation/16-data-repository/public/css/spectre-icons.min.css new file mode 100644 index 0000000..0276f7b --- /dev/null +++ b/implementation/16-data-repository/public/css/spectre-icons.min.css @@ -0,0 +1 @@ +/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/implementation/16-data-repository/public/css/spectre.min.css b/implementation/16-data-repository/public/css/spectre.min.css new file mode 100644 index 0000000..0fe23d9 --- /dev/null +++ b/implementation/16-data-repository/public/css/spectre.min.css @@ -0,0 +1 @@ +/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/implementation/16-data-repository/public/favicon.ico b/implementation/16-data-repository/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/16-data-repository/public/index.php b/implementation/16-data-repository/public/index.php new file mode 100644 index 0000000..d93da3a --- /dev/null +++ b/implementation/16-data-repository/public/index.php @@ -0,0 +1,5 @@ +getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render('hello', $data); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/16-data-repository/src/Action/Other.php b/implementation/16-data-repository/src/Action/Other.php new file mode 100644 index 0000000..da9ceaf --- /dev/null +++ b/implementation/16-data-repository/src/Action/Other.php @@ -0,0 +1,16 @@ +parse('This *works* **too!**'); + $response->getBody()->write($html); + return $response->withStatus(200); + } +} diff --git a/implementation/16-data-repository/src/Action/Page.php b/implementation/16-data-repository/src/Action/Page.php new file mode 100644 index 0000000..4af45f0 --- /dev/null +++ b/implementation/16-data-repository/src/Action/Page.php @@ -0,0 +1,60 @@ +repo->byName($page); + + // fix the next and previous buttons to work with our routing + $content = preg_replace('/\(\d\d-/m', '(', $page->content); + assert(is_string($content)); + $content = str_replace('.md)', ')', $content); + + $data = [ + 'title' => $page->title, + 'content' => $this->parser->parse($content), + ]; + + $html = $this->renderer->render('page/show', $data); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map(function (MarkdownPage $page) { + return [ + 'id' => $page->id, + 'title' => $page->content, + ]; + }, $this->repo->all()); + + $html = $this->renderer->render('page/list', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } +} diff --git a/implementation/16-data-repository/src/Bootstrap.php b/implementation/16-data-repository/src/Bootstrap.php new file mode 100644 index 0000000..3abc2e5 --- /dev/null +++ b/implementation/16-data-repository/src/Bootstrap.php @@ -0,0 +1,40 @@ +getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); diff --git a/implementation/16-data-repository/src/Exception/InternalServerError.php b/implementation/16-data-repository/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/16-data-repository/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +factory::fromGlobals(); + } + + /** + * @param UriInterface|string $uri + * @param array $serverParams + */ + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} diff --git a/implementation/16-data-repository/src/Factory/DoctrineEm.php b/implementation/16-data-repository/src/Factory/DoctrineEm.php new file mode 100644 index 0000000..b0be39b --- /dev/null +++ b/implementation/16-data-repository/src/Factory/DoctrineEm.php @@ -0,0 +1,32 @@ +settings->doctrine['devMode']); + + $config->setMetadataDriverImpl( + new AttributeDriver( + $this->settings->doctrine['metadataDirs'] + ) + ); + + return EntityManager::create( + $this->settings->connection, + $config, + ); + } +} diff --git a/implementation/16-data-repository/src/Factory/FileSystemSettingsProvider.php b/implementation/16-data-repository/src/Factory/FileSystemSettingsProvider.php new file mode 100644 index 0000000..f071078 --- /dev/null +++ b/implementation/16-data-repository/src/Factory/FileSystemSettingsProvider.php @@ -0,0 +1,22 @@ +filePath; + assert($settings instanceof Settings); + return $settings; + } +} diff --git a/implementation/16-data-repository/src/Factory/PipelineProvider.php b/implementation/16-data-repository/src/Factory/PipelineProvider.php new file mode 100644 index 0000000..77738f8 --- /dev/null +++ b/implementation/16-data-repository/src/Factory/PipelineProvider.php @@ -0,0 +1,25 @@ +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} diff --git a/implementation/16-data-repository/src/Factory/RequestFactory.php b/implementation/16-data-repository/src/Factory/RequestFactory.php new file mode 100644 index 0000000..2b17abc --- /dev/null +++ b/implementation/16-data-repository/src/Factory/RequestFactory.php @@ -0,0 +1,11 @@ +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = $settings; + $builder->addDefinitions($dependencies); + // $builder->enableCompilation('/tmp'); + return $builder->build(); + } +} diff --git a/implementation/16-data-repository/src/Factory/SettingsProvider.php b/implementation/16-data-repository/src/Factory/SettingsProvider.php new file mode 100644 index 0000000..ce1c5f0 --- /dev/null +++ b/implementation/16-data-repository/src/Factory/SettingsProvider.php @@ -0,0 +1,10 @@ +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + + echo $response->getBody(); + } +} diff --git a/implementation/16-data-repository/src/Http/ContainerPipeline.php b/implementation/16-data-repository/src/Http/ContainerPipeline.php new file mode 100644 index 0000000..816cedd --- /dev/null +++ b/implementation/16-data-repository/src/Http/ContainerPipeline.php @@ -0,0 +1,82 @@ + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} diff --git a/implementation/16-data-repository/src/Http/Emitter.php b/implementation/16-data-repository/src/Http/Emitter.php new file mode 100644 index 0000000..ce4c035 --- /dev/null +++ b/implementation/16-data-repository/src/Http/Emitter.php @@ -0,0 +1,10 @@ +getAttribute($this->routeAttributeName, false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} diff --git a/implementation/16-data-repository/src/Http/Pipeline.php b/implementation/16-data-repository/src/Http/Pipeline.php new file mode 100644 index 0000000..1a9dcda --- /dev/null +++ b/implementation/16-data-repository/src/Http/Pipeline.php @@ -0,0 +1,11 @@ +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} diff --git a/implementation/16-data-repository/src/Http/RoutedRequestHandler.php b/implementation/16-data-repository/src/Http/RoutedRequestHandler.php new file mode 100644 index 0000000..a7407c9 --- /dev/null +++ b/implementation/16-data-repository/src/Http/RoutedRequestHandler.php @@ -0,0 +1,10 @@ +pipeline->dispatch($request); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} diff --git a/implementation/16-data-repository/src/Model/MarkdownPage.php b/implementation/16-data-repository/src/Model/MarkdownPage.php new file mode 100644 index 0000000..df244fd --- /dev/null +++ b/implementation/16-data-repository/src/Model/MarkdownPage.php @@ -0,0 +1,13 @@ +dataPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return array_map(function (string $filename) { + $content = file_get_contents($filename); + if ($content === false) { + throw new InternalServerError('cannot read pages'); + } + $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); + return new MarkdownPage( + (int) substr($idAndTitle, 0, 2), + substr($idAndTitle, 3), + $content + ); + }, $files); + } + + public function byName(string $name): MarkdownPage + { + $pages = array_values( + array_filter( + $this->all(), + fn (MarkdownPage $p) => $p->title === $name, + ) + ); + + if (count($pages) !== 1) { + throw new NotFound; + } + + return $pages[0]; + } +} diff --git a/implementation/16-data-repository/src/Repository/MarkdownPageRepo.php b/implementation/16-data-repository/src/Repository/MarkdownPageRepo.php new file mode 100644 index 0000000..0792d32 --- /dev/null +++ b/implementation/16-data-repository/src/Repository/MarkdownPageRepo.php @@ -0,0 +1,15 @@ +engine->render($template, $data); + } +} diff --git a/implementation/16-data-repository/src/Template/ParsedownParser.php b/implementation/16-data-repository/src/Template/ParsedownParser.php new file mode 100644 index 0000000..2ffd287 --- /dev/null +++ b/implementation/16-data-repository/src/Template/ParsedownParser.php @@ -0,0 +1,17 @@ +parser->parse($markdown); + } +} diff --git a/implementation/16-data-repository/src/Template/Renderer.php b/implementation/16-data-repository/src/Template/Renderer.php new file mode 100644 index 0000000..ff916ed --- /dev/null +++ b/implementation/16-data-repository/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data = []): string; +} diff --git a/implementation/16-data-repository/templates/hello.html b/implementation/16-data-repository/templates/hello.html new file mode 100644 index 0000000..15a4cd2 --- /dev/null +++ b/implementation/16-data-repository/templates/hello.html @@ -0,0 +1,6 @@ +{{> partials/head }} +
+

Hello {{name}}

+

The time is {{now}}

+
+{{> partials/foot }} diff --git a/implementation/16-data-repository/templates/page.html b/implementation/16-data-repository/templates/page.html new file mode 100644 index 0000000..c3c5284 --- /dev/null +++ b/implementation/16-data-repository/templates/page.html @@ -0,0 +1,5 @@ +{{> partials/head }} +
+ {{{content}}} +
+{{> partials/foot }} diff --git a/implementation/16-data-repository/templates/page/list.html b/implementation/16-data-repository/templates/page/list.html new file mode 100644 index 0000000..bf42348 --- /dev/null +++ b/implementation/16-data-repository/templates/page/list.html @@ -0,0 +1,19 @@ + + + + + Pages + + + +
+ +
+ + \ No newline at end of file diff --git a/implementation/16-data-repository/templates/page/show.html b/implementation/16-data-repository/templates/page/show.html new file mode 100644 index 0000000..abe295e --- /dev/null +++ b/implementation/16-data-repository/templates/page/show.html @@ -0,0 +1,17 @@ + + + + + {{title}} + + + + + + +
+ {{{content}}} +
+ + \ No newline at end of file diff --git a/implementation/16-data-repository/templates/pagelist.html b/implementation/16-data-repository/templates/pagelist.html new file mode 100644 index 0000000..538e2c4 --- /dev/null +++ b/implementation/16-data-repository/templates/pagelist.html @@ -0,0 +1,11 @@ +{{> partials/head }} +
+ +
+{{> partials/foot }} diff --git a/implementation/16-data-repository/templates/partials/foot.html b/implementation/16-data-repository/templates/partials/foot.html new file mode 100644 index 0000000..17c7245 --- /dev/null +++ b/implementation/16-data-repository/templates/partials/foot.html @@ -0,0 +1,3 @@ +
+ + \ No newline at end of file diff --git a/implementation/16-data-repository/templates/partials/head.html b/implementation/16-data-repository/templates/partials/head.html new file mode 100644 index 0000000..421d387 --- /dev/null +++ b/implementation/16-data-repository/templates/partials/head.html @@ -0,0 +1,11 @@ + + + + + No Framework: {{title}} + + + + + +
From ececd7dcb5b37e7679831fa4b58dfd58cddf5acd Mon Sep 17 00:00:00 2001 From: lubiana Date: Wed, 6 Apr 2022 23:43:03 +0200 Subject: [PATCH 123/314] add perfomance chapters --- 15-adding-content.md | 4 +- 17-performance.md | 37 +- 18-caching.md | 252 ++ Vagrantfile | 8 +- app/composer.json | 5 +- app/composer.lock | 6 +- app/config/dependencies.php | 11 +- app/config/middlewares.php | 2 + app/data/pages/04-development-helpers.md | 4 +- app/data/pages/12-configuration.md | 1 - app/data/pages/13-refactoring.md | 4 - app/data/pages/14-middleware.md | 13 +- app/data/pages/15-adding-content.md | 253 ++ app/data/pages/16-data-repository.md | 265 ++ app/data/pages/17-performance.md | 43 + app/data/pages/18-caching.md | 252 ++ app/public/index.php | 2 +- app/src/Action/Page.php | 2 +- app/src/Factory/DoctrineEm.php | 32 - app/src/Middleware/Cache.php | 38 + app/src/Repository/CachedMarkdownPageRepo.php | 49 + app/src/Service/Cache/ApcuCache.php | 21 + app/src/Service/Cache/EasyCache.php | 9 + implementation/18-caching/.php-cs-fixer.php | 38 + implementation/18-caching/.phpcs.xml.dist | 9 + implementation/18-caching/cli-config.php | 13 + implementation/18-caching/composer.json | 57 + implementation/18-caching/composer.lock | 2440 +++++++++++++++++ .../18-caching/config/dependencies.php | 58 + .../18-caching/config/middlewares.php | 13 + implementation/18-caching/config/routes.php | 15 + implementation/18-caching/config/settings.php | 12 + .../data/pages/01-front-controller.md | 53 + .../18-caching/data/pages/02-composer.md | 75 + .../18-caching/data/pages/03-error-handler.md | 79 + .../data/pages/04-development-helpers.md | 260 ++ .../18-caching/data/pages/05-http.md | 124 + .../18-caching/data/pages/06-router.md | 101 + .../data/pages/07-dispatching-to-a-class.md | 137 + .../data/pages/08-inversion-of-control.md | 54 + .../data/pages/09-dependency-injector.md | 213 ++ .../18-caching/data/pages/10-invoker.md | 102 + .../18-caching/data/pages/11-templating.md | 236 ++ .../18-caching/data/pages/12-configuration.md | 200 ++ .../18-caching/data/pages/13-refactoring.md | 373 +++ .../18-caching/data/pages/14-middleware.md | 303 ++ .../data/pages/15-adding-content.md | 253 ++ .../data/pages/16-data-repository.md | 265 ++ .../18-caching/data/pages/17-performance.md | 43 + .../18-caching/data/pages/18-caching.md | 252 ++ .../18-caching/phpstan-baseline.neon | 7 + implementation/18-caching/phpstan.neon | 8 + .../18-caching/public/css/spectre-exp.min.css | 1 + .../public/css/spectre-icons.min.css | 1 + .../18-caching/public/css/spectre.min.css | 1 + implementation/18-caching/public/favicon.ico | Bin 0 -> 15086 bytes implementation/18-caching/public/index.php | 5 + implementation/18-caching/src/.gitkeep | 0 .../18-caching/src/Action/Hello.php | 31 + .../18-caching/src/Action/Other.php | 16 + implementation/18-caching/src/Action/Page.php | 60 + implementation/18-caching/src/Bootstrap.php | 40 + .../src/Exception/InternalServerError.php | 9 + .../src/Exception/MethodNotAllowed.php | 9 + .../18-caching/src/Exception/NotFound.php | 9 + .../src/Factory/ContainerProvider.php | 10 + .../src/Factory/DiactorosRequestFactory.php | 28 + .../Factory/FileSystemSettingsProvider.php | 22 + .../src/Factory/PipelineProvider.php | 25 + .../18-caching/src/Factory/RequestFactory.php | 11 + .../src/Factory/SettingsContainerProvider.php | 26 + .../src/Factory/SettingsProvider.php | 10 + .../18-caching/src/Http/BasicEmitter.php | 38 + .../18-caching/src/Http/ContainerPipeline.php | 82 + .../18-caching/src/Http/Emitter.php | 10 + .../src/Http/InvokerRoutedHandler.php | 34 + .../18-caching/src/Http/Pipeline.php | 11 + .../18-caching/src/Http/RouteMiddleware.php | 69 + .../src/Http/RoutedRequestHandler.php | 10 + implementation/18-caching/src/Kernel.php | 32 + .../18-caching/src/Middleware/Cache.php | 38 + .../18-caching/src/Model/MarkdownPage.php | 13 + .../src/Repository/CachedMarkdownPageRepo.php | 49 + .../Repository/FileSystemMarkdownPageRepo.php | 61 + .../src/Repository/MarkdownPageRepo.php | 15 + .../src/Service/Cache/ApcuCache.php | 21 + .../src/Service/Cache/EasyCache.php | 9 + .../18-caching/src/Service/Time/Now.php | 10 + .../src/Service/Time/SystemClockNow.php | 13 + implementation/18-caching/src/Settings.php | 16 + .../src/Template/MarkdownParser.php | 8 + .../src/Template/MustacheRenderer.php | 17 + .../src/Template/ParsedownParser.php | 17 + .../18-caching/src/Template/Renderer.php | 11 + .../18-caching/templates/hello.html | 6 + implementation/18-caching/templates/page.html | 5 + .../18-caching/templates/page/list.html | 19 + .../18-caching/templates/page/show.html | 17 + .../18-caching/templates/pagelist.html | 11 + .../18-caching/templates/partials/foot.html | 3 + .../18-caching/templates/partials/head.html | 11 + 101 files changed, 8014 insertions(+), 62 deletions(-) create mode 100644 18-caching.md create mode 100644 app/data/pages/15-adding-content.md create mode 100644 app/data/pages/16-data-repository.md create mode 100644 app/data/pages/17-performance.md create mode 100644 app/data/pages/18-caching.md delete mode 100644 app/src/Factory/DoctrineEm.php create mode 100644 app/src/Middleware/Cache.php create mode 100644 app/src/Repository/CachedMarkdownPageRepo.php create mode 100644 app/src/Service/Cache/ApcuCache.php create mode 100644 app/src/Service/Cache/EasyCache.php create mode 100644 implementation/18-caching/.php-cs-fixer.php create mode 100644 implementation/18-caching/.phpcs.xml.dist create mode 100644 implementation/18-caching/cli-config.php create mode 100644 implementation/18-caching/composer.json create mode 100644 implementation/18-caching/composer.lock create mode 100644 implementation/18-caching/config/dependencies.php create mode 100644 implementation/18-caching/config/middlewares.php create mode 100644 implementation/18-caching/config/routes.php create mode 100644 implementation/18-caching/config/settings.php create mode 100644 implementation/18-caching/data/pages/01-front-controller.md create mode 100644 implementation/18-caching/data/pages/02-composer.md create mode 100644 implementation/18-caching/data/pages/03-error-handler.md create mode 100644 implementation/18-caching/data/pages/04-development-helpers.md create mode 100644 implementation/18-caching/data/pages/05-http.md create mode 100644 implementation/18-caching/data/pages/06-router.md create mode 100644 implementation/18-caching/data/pages/07-dispatching-to-a-class.md create mode 100644 implementation/18-caching/data/pages/08-inversion-of-control.md create mode 100644 implementation/18-caching/data/pages/09-dependency-injector.md create mode 100644 implementation/18-caching/data/pages/10-invoker.md create mode 100644 implementation/18-caching/data/pages/11-templating.md create mode 100644 implementation/18-caching/data/pages/12-configuration.md create mode 100644 implementation/18-caching/data/pages/13-refactoring.md create mode 100644 implementation/18-caching/data/pages/14-middleware.md create mode 100644 implementation/18-caching/data/pages/15-adding-content.md create mode 100644 implementation/18-caching/data/pages/16-data-repository.md create mode 100644 implementation/18-caching/data/pages/17-performance.md create mode 100644 implementation/18-caching/data/pages/18-caching.md create mode 100644 implementation/18-caching/phpstan-baseline.neon create mode 100644 implementation/18-caching/phpstan.neon create mode 100644 implementation/18-caching/public/css/spectre-exp.min.css create mode 100644 implementation/18-caching/public/css/spectre-icons.min.css create mode 100644 implementation/18-caching/public/css/spectre.min.css create mode 100644 implementation/18-caching/public/favicon.ico create mode 100644 implementation/18-caching/public/index.php create mode 100644 implementation/18-caching/src/.gitkeep create mode 100644 implementation/18-caching/src/Action/Hello.php create mode 100644 implementation/18-caching/src/Action/Other.php create mode 100644 implementation/18-caching/src/Action/Page.php create mode 100644 implementation/18-caching/src/Bootstrap.php create mode 100644 implementation/18-caching/src/Exception/InternalServerError.php create mode 100644 implementation/18-caching/src/Exception/MethodNotAllowed.php create mode 100644 implementation/18-caching/src/Exception/NotFound.php create mode 100644 implementation/18-caching/src/Factory/ContainerProvider.php create mode 100644 implementation/18-caching/src/Factory/DiactorosRequestFactory.php create mode 100644 implementation/18-caching/src/Factory/FileSystemSettingsProvider.php create mode 100644 implementation/18-caching/src/Factory/PipelineProvider.php create mode 100644 implementation/18-caching/src/Factory/RequestFactory.php create mode 100644 implementation/18-caching/src/Factory/SettingsContainerProvider.php create mode 100644 implementation/18-caching/src/Factory/SettingsProvider.php create mode 100644 implementation/18-caching/src/Http/BasicEmitter.php create mode 100644 implementation/18-caching/src/Http/ContainerPipeline.php create mode 100644 implementation/18-caching/src/Http/Emitter.php create mode 100644 implementation/18-caching/src/Http/InvokerRoutedHandler.php create mode 100644 implementation/18-caching/src/Http/Pipeline.php create mode 100644 implementation/18-caching/src/Http/RouteMiddleware.php create mode 100644 implementation/18-caching/src/Http/RoutedRequestHandler.php create mode 100644 implementation/18-caching/src/Kernel.php create mode 100644 implementation/18-caching/src/Middleware/Cache.php create mode 100644 implementation/18-caching/src/Model/MarkdownPage.php create mode 100644 implementation/18-caching/src/Repository/CachedMarkdownPageRepo.php create mode 100644 implementation/18-caching/src/Repository/FileSystemMarkdownPageRepo.php create mode 100644 implementation/18-caching/src/Repository/MarkdownPageRepo.php create mode 100644 implementation/18-caching/src/Service/Cache/ApcuCache.php create mode 100644 implementation/18-caching/src/Service/Cache/EasyCache.php create mode 100644 implementation/18-caching/src/Service/Time/Now.php create mode 100644 implementation/18-caching/src/Service/Time/SystemClockNow.php create mode 100644 implementation/18-caching/src/Settings.php create mode 100644 implementation/18-caching/src/Template/MarkdownParser.php create mode 100644 implementation/18-caching/src/Template/MustacheRenderer.php create mode 100644 implementation/18-caching/src/Template/ParsedownParser.php create mode 100644 implementation/18-caching/src/Template/Renderer.php create mode 100644 implementation/18-caching/templates/hello.html create mode 100644 implementation/18-caching/templates/page.html create mode 100644 implementation/18-caching/templates/page/list.html create mode 100644 implementation/18-caching/templates/page/show.html create mode 100644 implementation/18-caching/templates/pagelist.html create mode 100644 implementation/18-caching/templates/partials/foot.html create mode 100644 implementation/18-caching/templates/partials/head.html diff --git a/15-adding-content.md b/15-adding-content.md index c894d66..64562fa 100644 --- a/15-adding-content.md +++ b/15-adding-content.md @@ -1,4 +1,4 @@ -[<< previous](14-middleware.md) | [next >>](14-invoker.md) +[<< previous](14-middleware.md) | [next >>](16-data-repository.md) ### Adding Content @@ -250,4 +250,4 @@ add even more lines to that simple class, so lets move on to the next chapter wh classes following our holy SOLID principles :) -[<< previous](14-middleware.md) | [next >>](14-invoker.md) +[<< previous](14-middleware.md) | [next >>](16-data-repository.md) diff --git a/17-performance.md b/17-performance.md index d457bff..c83c7d5 100644 --- a/17-performance.md +++ b/17-performance.md @@ -1,6 +1,6 @@ -[<< previous](15-adding-content.md) | [next >>](17-performance.md) +[<< previous](16-data-repository.md) | [next >>](18-caching.md) -## Performance +## Autoloading performance Although our application is still very small and you should not really experience any performance issues right now, there are still some things we can already consider and take a look at. If I check the network tab in my browser it takes @@ -10,11 +10,34 @@ a template, some config files here and there and parse some markdown. So that sh The problem is, that we heavily rely on autoloading for all our class files, in the `src` folder. And there are also quite a lot of other files in composers `vendor` directory. To understand while this is becomming we should make -ourselves familiar with how autoloading in PHP works. +ourselves familiar with how [autoloading in php](https://www.php.net/manual/en/language.oop5.autoload.php) works. -[autoloading in php](https://www.php.net/manual/en/language.oop5.autoload.php) -[composer autoloader optimization](https://getcomposer.org/doc/articles/autoloader-optimization.md) +The basic idea is, that every class that php encounters has to be loaded from somewhere in the filesystem, we could +just require the files manually but that is tedious, unflexible and can often cause errors. -### Composer autoloading +The problem we are now facing is that the composer autoloader has some rules to determine from where in the filesystem +a class definition might be placed, then the autoloader tries to locate a file by the namespace and classname and if it +exists includes that file. -[<< previous](15-adding-content.md) | [next >>](17-performance.md) +If we only have a handfull of classes that does not take a lot of time, but as we are growing with our application this +easily takes longer than necesery, but fortunately composer has some options to speed up the class loading. + +Take a few minutes to read the documentation about [composer autoloader optimization](https://getcomposer.org/doc/articles/autoloader-optimization.md) + +You can try all 3 levels of optimizations, but we are going to stick with the first one for now, so lets create an +optimized classmap. + +`composer dump-autoload -o` + +After composer has finished you can start the devserver again with `composer serve` and take a look at the network tab +in your browsers devtools. + +In my case the response time falls down to under an average of 30ms with some spikes in between, but all in all it looks really good. +You can also try out the different optimization levels and see if you can spot any differences. + +Although the composer manual states not to use the optimtization in a dev environment I personally have not encountered +any errors with the first level of optimizations, so we can use that level here. If you add the line from the documentation +to your `composer.json` so that the autoloader gets optimized everytime we install new packages. + + +[<< previous](16-data-repository.md) | [next >>](18-caching.md) diff --git a/18-caching.md b/18-caching.md new file mode 100644 index 0000000..f06abe9 --- /dev/null +++ b/18-caching.md @@ -0,0 +1,252 @@ +[<< previous](17-performance.md) | [next >>](19-database.md) + +**DISClAIMER** I do not really have a lot of experience when it comes to caching, so this chapter is mostly some random +thoughts and ideas I wanted to explore when writing this tutorial, you should definitely take everything that is being +said here with caution and try to read up on some other sources. But that holds true for the whole tutorial anyway :) + +## Caching + +In the last chapter we greatly improved the perfomance for the lookup of all our classfiles, but currently we do not +have any real bottlenecks in our application like complex queries. + +But in a real application we are going to execute some really heavy and time intensive database queries that can take +quite a while to be completed. + +We can simulate that by adding a simple delay in our `FileSystemMarkdownPageRepo`. + +```php + return array_map(function (string $filename) { + usleep(rand(100, 400) * 1000); + $content = file_get_contents($filename); + if ($content === false) { + throw new InternalServerError('cannot read pages'); + } + $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); + return new MarkdownPage( + (int) substr($idAndTitle, 0, 2), + substr($idAndTitle, 3), + $content + ); +}); +``` + +Here I added a function that pauses the scripts execution for a random time between 100 and 400ms for every markdownpage +in every call of the `all()` method. + +If you open any page or even the listAction in you browser you will see, that it takes quite a time to render that page. +Although this is a silly example we do not really need to query the database on every request, so lets add a way to cache +the database results between requests. + +The PHP-Community has already adressed the issue of having easy to use access to cache libraries, there is the +[PSR-6 Caching Interface](https://www.php-fig.org/psr/psr-6) which gives us easy access to many different implementations, +then there is also a much simpler [PSR-16 Simple Cache](https://www.php-fig.org/psr/psr-16) which makes the use even more +easy, and most Caching Libraries implement Both interfaces anyway. You would think that this is more than enough solutions +to satisfy all the Caching needs around, but the Symfony People decided that Caching should be even simpler and easier +to use and defined their own [Interface](https://symfony.com/doc/current/components/cache.html#cache-component-contracts) +which only needs two methods. You should definitely take a look at the linked documentation as it really blew my mind +when I first encountered it. + +The basic idea is that you provide a callback that computes the requested value. The Cache implementation then checks +if it already has the value stored somewhere and if it doesnt it just executes the callback and stores the value for +future calls. + +It is really simple and great to use. In a real world application you should definitely use that or a PSR-16 implementation +but for this tutorial I wanted to roll out my own solution, so here we go. + +As always we are going to define an interface first, I am going to call it EasyCache and place it in the `Service/Cache` +namespace. I will require only one method which is base on the Symfony Cache Contract, and hast a key, a callback, and +the duration that the item should be cached as arguments. + +```php +cache->get( + $key, + fn () => $this->repo->all(), + 300 + ); + } + + public function byName(string $name): MarkdownPage + { + $key = base64_encode(self::class . 'byName' . $name); + return $this->cache->get( + $key, + fn () => $this->repo->byName($name), + 300 + ); + } +} +``` + +This simple wrapper just requires an EasyCache implementation and a MarkdownPageRepo in the constructor and uses them +to cache all queries for 5 minutes. The beauty is that we are not dependent on any implementation here, so we can switch +out the Repository or the Cache at any point down the road if we want to. + +In order to use that we need to update our `config/dependencies.php` to add an alias for the EasyCache interface as well +as defining our CachedMarkdownPageRepo as implementation for the MarkdownPageRepo interface: + +```php +MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, +EasyCache::class => fn (ApcuCache $c) => $c, +``` + +If we try to access our webpage now, we are getting an error, as PHP-DI has detected a circular dependency that cannot +be autowired. + +The Problem is that our CachedMarkdownPageRepo ist defined as the implementation for the MarkdownPageRepo, but it also +requires that exact interface as a dependency. To resolve this issue we need to manually tell the container how to build +the CachedMarkdownPageRepo by adding another line to the `config/dependencies.php` file: + +```php +CachedMarkdownPageRepo::class => fn (EasyCache $c, FileSystemMarkdownPageRepo $r) => new CachedMarkdownPageRepo($c, $r), +``` + +Here we explicitly require the FileSystemMarkdownPageRepo and us that to create the CachedMarkdownPageRepo object. + +When you now navigate to the pages list or to a specific page the first load should take a while (because of our added delay) +but the following request should be answered blazingly fast. + +Before moving on to the next chapter we can take the caching approach even further, in the middleware chapter I talked +about a simple CachingMiddleware that caches all the GET-Request for some seconds, as they should not change that often, +and we can bypass most of our application logic if we just complelety cache away the responses our application generates, +and return them quite early in our Middleware-Pipeline befor the router gets called, or the invoker calls the action, +which itself uses some other services to fetch all the needed data. + +We will introduce a new `Middleware` namespace to place our `Cache.php` middleware: +```php +getMethod() !== 'GET') { + return $handler->handle($request); + } + $keyHash = base64_encode($request->getUri()->getPath()); + $result = $this->cache->get( + $keyHash, + fn () => $this->serializer::toString($handler->handle($request)), + 300 + ); + return $this->serializer::fromString($result); + } +} +``` + +The code is quite straight forward, but you might be confused by the Responseserializer I have added here, we need this +because the response body is a stream object, which doesnt always gets serialized correctly, therefore I use a class from +the laminas project to to all the heavy lifting for us. + +We need to add the now middleware to the `config/middlewares.php` file. + +```php +>](19-database.md) diff --git a/Vagrantfile b/Vagrantfile index 56a4db4..7cdc936 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -3,9 +3,13 @@ Vagrant.configure("2") do |config| config.vm.box = "archlinux/archlinux" + config.vm.provider "virtualbox" do |v| + v.memory = 2048 + v.cpus = 4 + end config.vm.network "forwarded_port", guest: 1234, host: 1234 config.vm.network "forwarded_port", guest: 22, host: 2200, id: 'ssh' - config.vm.synced_folder "./app", "/home/vagrant/app" + config.vm.synced_folder "./app", "/home/vagrant/app/" config.ssh.username = 'vagrant' config.ssh.password = 'vagrant' config.vm.provision "shell", inline: <<-SHELL @@ -17,5 +21,7 @@ Vagrant.configure("2") do |config| echo -e 'zend_extension=xdebug\nxdebug.client_host=10.0.2.2\n' >> /etc/php/conf.d/tutorial.ini echo -e 'xdebug.client_port=9003\nxdebug.mode=debug\n' >> /etc/php/conf.d/tutorial.ini echo -e 'zend.assertions=1\n' >> /etc/php/conf.d/tutorial.ini + echo -e 'opcache.enable=1\nopcache.enable_cli=1\n' >> /etc/php/conf.d/tutorial.ini + echo -e 'acp.enable=1\napc.enable_cli=1\n' >> /etc/php/conf.d/tutorial.ini SHELL end diff --git a/app/composer.json b/app/composer.json index b5c7f1a..29695da 100644 --- a/app/composer.json +++ b/app/composer.json @@ -12,7 +12,9 @@ "middlewares/trailing-slash": "^2.0", "middlewares/whoops": "^2.0", "erusev/parsedown": "^1.7", - "league/commonmark": "^2.2" + "league/commonmark": "^2.2", + "ext-apcu": "*", + "ext-zend-opcache": "*" }, "autoload": { "psr-4": { @@ -36,6 +38,7 @@ "mnapoli/hard-mode": "^0.3.0" }, "config": { + "optimize-autoloader": true, "allow-plugins": { "phpstan/extension-installer": true, "dealerdirect/phpcodesniffer-composer-installer": true diff --git a/app/composer.lock b/app/composer.lock index a62d9c7..40cd7d3 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "00acf07ae222f9117a84bce157b99837", + "content-hash": "9a29468fd456190a9fbcff98ed42d862", "packages": [ { "name": "dflydev/dot-access-data", @@ -2431,7 +2431,9 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^8.1" + "php": "^8.1", + "ext-apcu": "*", + "ext-zend-opcache": "*" }, "platform-dev": [], "plugin-api-version": "2.3.0" diff --git a/app/config/dependencies.php b/app/config/dependencies.php index 0040933..df815c6 100644 --- a/app/config/dependencies.php +++ b/app/config/dependencies.php @@ -11,8 +11,11 @@ use Lubian\NoFramework\Http\InvokerRoutedHandler; use Lubian\NoFramework\Http\Pipeline; use Lubian\NoFramework\Http\RoutedRequestHandler; use Lubian\NoFramework\Http\RouteMiddleware; +use Lubian\NoFramework\Repository\CachedMarkdownPageRepo; use Lubian\NoFramework\Repository\FileSystemMarkdownPageRepo; use Lubian\NoFramework\Repository\MarkdownPageRepo; +use Lubian\NoFramework\Service\Cache\ApcuCache; +use Lubian\NoFramework\Service\Cache\EasyCache; use Lubian\NoFramework\Service\Time\Now; use Lubian\NoFramework\Service\Time\SystemClockNow; use Lubian\NoFramework\Settings; @@ -26,8 +29,6 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; -use Symfony\Component\Cache\Adapter\FilesystemAdapter; -use Symfony\Contracts\Cache\CacheInterface; use function FastRoute\simpleDispatcher; @@ -39,9 +40,11 @@ return [ MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, - CacheInterface::class => fn (FilesystemAdapter $a) => $a, MarkdownParser::class => fn (ParsedownParser $p) => $p, - MarkdownPageRepo::class => fn (FileSystemMarkdownPageRepo $r) => $r, + MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, + EasyCache::class => fn (ApcuCache $c) => $c, + CachedMarkdownPageRepo::class => fn (EasyCache $c, FileSystemMarkdownPageRepo $r) => new CachedMarkdownPageRepo($c, $r), + // Factories ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), diff --git a/app/config/middlewares.php b/app/config/middlewares.php index 71dd461..ab662be 100644 --- a/app/config/middlewares.php +++ b/app/config/middlewares.php @@ -1,11 +1,13 @@ >](14-invoker.md) +[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) ### Middleware @@ -153,8 +153,6 @@ class ContainerPipeline implements Pipeline { /** * @param array $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container */ public function __construct( private array $middlewares, @@ -295,4 +293,11 @@ Lets try if you can make the kernel work with our created Pipeline implementatio pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a dependency container, if you want you can do that as well. -[<< previous](12-refactoring.md) | [next >>](14-invoker.md) +**A quick note about docblocks:** You might have noticed, that I rarely add docblocks to my the code in the examples, and +when I do it seems kind of random. My philosophy is that I only add docblocks when there is no way to automatically get +the exact type from the code itself. For me docblocks only serve two purposes: help my IDE to understand what it choices +it has for code completion and to help the static analysis to better understand the code. There is a great blogpost +about the [cost and value of DocBlocks](https://localheinz.com/blog/2018/05/06/cost-and-value-of-docblocks/), although it +is written in 2018 at a time before PHP 7.4 was around everything written there is still valid today. + +[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) diff --git a/app/data/pages/15-adding-content.md b/app/data/pages/15-adding-content.md new file mode 100644 index 0000000..64562fa --- /dev/null +++ b/app/data/pages/15-adding-content.md @@ -0,0 +1,253 @@ +[<< previous](14-middleware.md) | [next >>](16-data-repository.md) + +### Adding Content + +By now we did not really display anything but some examples to in our application and it is now time to make our app +display some content. For example we could our app be able to display the Markdown files used in this tutorial as +nicely rendered HTML Pages that can be viewed in the browser instead of the editor you are using. + +So lets start by copying the markdown files to our app directory. I have created a new folder 'data/pages' and placed all +the markdown files in there. + +Next we need a markdown parser, a pretty simple one is [Parsedown](https://parsedown.org/), if you want more features +you could also use the [Commonmark parser](https://commonmark.thephpleague.com/). I will choose Parsedown here, but you +can use whatever you like. + +After installing Parsedown lets write a Markdownparser interface and an implementation using parsedown. + +We only need one function that receives a string of Markdown and returns the HTML represantion (as a string as well). + + + +```php +parser->parse($markdown); + } +} +``` + +We could now use the ParsedownRender class directly in our actions by typehinting the classname as an argument to the +constructor or a method, but as we always want to rely on an interface instead of an implementation we need to define +the the ParsedownRenderer as the correct implementation for the MarkdownRenderer interface in the dependencies file: + +```php +... + \Lubian\NoFramework\Template\MarkdownParser::class => fn(\Lubian\NoFramework\Template\ParsedownParser $p) => $p, +... +``` + +You can test that in our "Other.php" action and try out if the Parser works and is able to render Markdown to HTML: + +```php +public function someFunctionName(ResponseInterface $response, MarkdownParser $parser): ResponseInterface +{ + $html = $parser->parse('This *works* **too!**'); + $response->getBody()->write($html); + return $response->withStatus(200); +} +``` + +But we want to display complete Pages written in Markdown, it would also be neat to be able to display a list of all +available pages. For that we need a few things: + +Firstly we need two new Templates, one for the list of the Pages, and the second one for displaying a single pages +content. Create a new folder in `templates/page` with to files: + +`templates/page/list.html` +```html + + + + + Pages + + + +
+ +
+ + +``` + +This template iterates over a provided array of pages, each element consists of the two properties: an id and a title, +those are simply displayed using an unordered list. + +`templates/page/show.html` +```html + + + + + {{title}} + + + + + + +
+ {{{content}}} +
+ + +``` + +The second templates displays a single rendered markdown page. As data it expects the title and the content as array. +I used an extra bracket for the content ```{{{content}}}``` so that the Mustache-Renderer does not escape the provided +html and thereby destroys the the parsed markdown. + +You might have spotted that I added [Pico.css](https://picocss.com/) which is just a very small css framework to make the +pages a little bit nicer to look at. It mostly provides some typography styles that work great with rendered Markdown, +but you can leave that out or use any other css framework you like. There is also some Javascript that adds syntax +highlighting to the code. + +After you have taken care of the templating side we can now create an new Action class with two methods to display use +our markdown files and the templates to create the pages. As we have two templates I propose to use Two methods in our +Action: +`src/Action/Page.php` +```php +function show(string $name): \Psr\Http\Message\ResponseInterface; +function list(): \Psr\Http\Message\ResponseInterface; +``` + +Lets define two routes. `/page` should display the overview of all pages, and if the add the name of chapter to the +route, `/page/adding-content` for example, the show action should be called with the name as a variable: + +`config/routes.php` +```php +$r->addRoute('GET', '/page', [Page::class, 'list']); +$r->addRoute('GET', '/page/{page}', [Page::class, 'show']); +``` + +Here is my Implementation. If have added a little regex replacement in the show method that replaces the links to the +next and previous chapter so that it works with our routing configuration. + +`src/Action/Page.php` +```php +getPages(), + fn (string $filename) => str_contains($filename, $page) + ) + )[0]; + $markdown = file_get_contents($page); + + // fix the next and previous buttons to work with our routing + $markdown = preg_replace('/\(\d\d-/m', '(', $markdown); + $markdown = str_replace('.md)', ')', $markdown); + + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + $data = [ + 'title' => substr($page, 3), + 'content' => $this->parser->parse($markdown), + ]; + $html = $this->renderer->render('page/show', $data); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map(function (string $page) { + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + return [ + 'id' => substr($page, 0, 2), + 'title' => substr($page, 3), + ]; + }, $this->getPages()); + $html = $this->renderer->render('page/list', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } + + /** + * @return string[] + */ + private function getPages(): array + { + $files = glob($this->pagesPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return $files; + } +} +``` + +You can now navigate your Browser to [localhost:1234/page][http://localhost:1234/page] and try out if everything works. + +Of course this code is far from looking good. We heavily rely on the pages being files in the filesystem, and the action +should never be aware of the filesystem in the first place, also we have a lot of string replacements and other repetetive +code in the file. And phpstan is gonna scream at us a lot, but if we rewrite the code to satisfy all the checks we would +add even more lines to that simple class, so lets move on to the next chapter where we move all the logic to seperate +classes following our holy SOLID principles :) + + +[<< previous](14-middleware.md) | [next >>](16-data-repository.md) diff --git a/app/data/pages/16-data-repository.md b/app/data/pages/16-data-repository.md new file mode 100644 index 0000000..d9a3218 --- /dev/null +++ b/app/data/pages/16-data-repository.md @@ -0,0 +1,265 @@ +[<< previous](15-adding-content.md) | [next >>](17-performance.md) + +## Data Repository + +At the end of the last chapter I mentioned being unhappy with our Pages action, because there is to much stuff happening +there. We are firstly receiving some Arguments, then we are using those to query the filesytem for the given page, +loading the specific file from the filesystem, rendering the markdown, passing the markdown to the template renderer, +adding the resulting html to the response and then returning the response. + +In order to make our pageaction independent from the filesystem and move the code that is responsible for reading the +files +to a better place I want to introduce +the [Repository Pattern](https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html). + +I want to start by creating a class that represents the Data that is included in a page so that. For now I can spot +three +distrinct attributes. + +* the ID (or chapternumber) +* the title (or name) +* the content + +Currently all those properties are always available, but we might later be able to create new pages and store them, but +at that point in time we are not yet aware of the new available ID, so we should leave that property nullable. This +allows +us to create an object without an id and let the code that actually saves the object to a persistant store define a +valid +id on saving. + +Lets create an new Namespace called `Model` and put a `MarkdownPage.php` class in there: + +```php +dataPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return array_map(function (string $filename) { + $content = file_get_contents($filename); + if ($content === false) { + throw new InternalServerError('cannot read pages'); + } + $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); + return new MarkdownPage( + (int) substr($idAndTitle, 0, 2), + substr($idAndTitle, 3), + $content + ); + }, $files); + } + + public function byName(string $name): MarkdownPage + { + $pages = array_values( + array_filter( + $this->all(), + fn (MarkdownPage $p) => $p->title === $name, + ) + ); + + if (count($pages) !== 1) { + throw new NotFound; + } + + return $pages[0]; + } +} +``` + +With that in place we need to add the required `$pagesPath` to our settings class and add specify that in our +configuration. + +`src/Settings.php` + +```php +final class Settings +{ + public function __construct( + public readonly string $environment, + public readonly string $dependenciesFile, + public readonly string $middlewaresFile, + public readonly string $templateDir, + public readonly string $templateExtension, + public readonly string $pagesPath, + ) { + } +} +``` + +`config/settings.php` + +```php +return new Settings( + environment: 'prod', + dependenciesFile: __DIR__ . '/dependencies.php', + middlewaresFile: __DIR__ . '/middlewares.php', + templateDir: __DIR__ . '/../templates', + templateExtension: '.html', + pagesPath: __DIR__ . '/../data/pages/', +); +``` + +Of course we need to define the correct implementation for the container to choose when we are requesting the Repository +interface: +`conf/dependencies.php` + +```php +MarkdownPageRepo::class => fn (FileSystemMarkdownPageRepo $r) => $r, +FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), +``` + +Now you can request the MarkdownPageRepo Interface in your page action and use the defined functions to get the +MarkdownPage +Objects. My `src/Action/Page.php` looks like this now: + +```php +repo->byName($page); + + // fix the next and previous buttons to work with our routing + $content = preg_replace('/\(\d\d-/m', '(', $page->content); + assert(is_string($content)); + $content = str_replace('.md)', ')', $content); + + $data = [ + 'title' => $page->title, + 'content' => $this->parser->parse($content), + ]; + + $html = $this->renderer->render('page/show', $data); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map(function (MarkdownPage $page) { + return [ + 'id' => $page->id, + 'title' => $page->content, + ]; + }, $this->repo->all()); + + $html = $this->renderer->render('page/list', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } +} +``` + +Check the page in your browser if everything still works, don't forget to run phpstan and the others fixers before +committing your changes and moving on to the next chapter. + +[<< previous](15-adding-content.md) | [next >>](17-performance.md) diff --git a/app/data/pages/17-performance.md b/app/data/pages/17-performance.md new file mode 100644 index 0000000..c83c7d5 --- /dev/null +++ b/app/data/pages/17-performance.md @@ -0,0 +1,43 @@ +[<< previous](16-data-repository.md) | [next >>](18-caching.md) + +## Autoloading performance + +Although our application is still very small and you should not really experience any performance issues right now, +there are still some things we can already consider and take a look at. If I check the network tab in my browser it takes +about 90-400ms to show a simple rendered markdownpage, with is sort of ok but in my opinion way to long as we are not +really doing anything and do not connect to any external services. Mostly we are just reading around 16 markdown files, +a template, some config files here and there and parse some markdown. So that should not really take that long. + +The problem is, that we heavily rely on autoloading for all our class files, in the `src` folder. And there are also +quite a lot of other files in composers `vendor` directory. To understand while this is becomming we should make +ourselves familiar with how [autoloading in php](https://www.php.net/manual/en/language.oop5.autoload.php) works. + +The basic idea is, that every class that php encounters has to be loaded from somewhere in the filesystem, we could +just require the files manually but that is tedious, unflexible and can often cause errors. + +The problem we are now facing is that the composer autoloader has some rules to determine from where in the filesystem +a class definition might be placed, then the autoloader tries to locate a file by the namespace and classname and if it +exists includes that file. + +If we only have a handfull of classes that does not take a lot of time, but as we are growing with our application this +easily takes longer than necesery, but fortunately composer has some options to speed up the class loading. + +Take a few minutes to read the documentation about [composer autoloader optimization](https://getcomposer.org/doc/articles/autoloader-optimization.md) + +You can try all 3 levels of optimizations, but we are going to stick with the first one for now, so lets create an +optimized classmap. + +`composer dump-autoload -o` + +After composer has finished you can start the devserver again with `composer serve` and take a look at the network tab +in your browsers devtools. + +In my case the response time falls down to under an average of 30ms with some spikes in between, but all in all it looks really good. +You can also try out the different optimization levels and see if you can spot any differences. + +Although the composer manual states not to use the optimtization in a dev environment I personally have not encountered +any errors with the first level of optimizations, so we can use that level here. If you add the line from the documentation +to your `composer.json` so that the autoloader gets optimized everytime we install new packages. + + +[<< previous](16-data-repository.md) | [next >>](18-caching.md) diff --git a/app/data/pages/18-caching.md b/app/data/pages/18-caching.md new file mode 100644 index 0000000..42e9cb1 --- /dev/null +++ b/app/data/pages/18-caching.md @@ -0,0 +1,252 @@ +[<< previous](17-performance.md) | [next >>](19-database.md) + +**DISClAIMER** I do not really have a lot of experience when it comes to caching, so this chapter is mostly some random +thoughts and ideas I wanted to explore when writing this tutorial, you should definitely take everything that is being +said here with caution and try to read up on some other sources. But that holds true for the whole tutorial anyway :) + +## Caching + +In the last chapter we greatly improved the perfomance for the lookup of all our classfiles, but currently we do not +have any real bottlenecks in our application like complex queries. + +But in a real application we are going to execute some really heavy and time intensive database queries that can take +quite a while to be completed. + +We can simulate that by adding a simple delay in our `FileSystemMarkdownPageRepo`. + +```php + return array_map(function (string $filename) { + usleep(rand(100, 400) * 1000); + $content = file_get_contents($filename); + if ($content === false) { + throw new InternalServerError('cannot read pages'); + } + $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); + return new MarkdownPage( + (int) substr($idAndTitle, 0, 2), + substr($idAndTitle, 3), + $content + ); +}); +``` + +Here I added a function that pauses the scripts execution for a random time between 100 and 400ms for every markdownpage +in every call of the `all()` method. + +If you open any page or even the listAction in you browser you will see, that it takes quite a time to render that page. +Although this is a silly example we do not really need to query the database on every request, so lets add a way to cache +the database results between requests. + +The PHP-Community has already adressed the issue of having easy to use access to cache libraries, there is the +[PSR-6 Caching Interface](https://www.php-fig.org/psr/psr-6) which gives us easy access to many different implementations, +then there is also a much simpler [PSR-16 Simple Cache](https://www.php-fig.org/psr/psr-16) which makes the use even more +easy, and most Caching Libraries implement Both interfaces anyway. You would think that this is more than enough solutions +to satisfy all the Caching needs around, but the Symfony People decided that Caching should be even simpler and easier +to use and defined their own [Interface](https://symfony.com/doc/current/components/cache.html#cache-component-contracts) +which only needs two methods. You should definitely take a look at the linked documentation as it really blew my mind +when I first encountered it. + +The basic idea is that you provide a callback that computes the requested value. The Cache implementation then checks +if it already has the value stored somewhere and if it doesnt it just executes the callback and stores the value for +future calls. + +It is really simple and great to use. In a real world application you should definitely use that or a PSR-16 implementation +but for this tutorial I wanted to roll out my own solution, so here we go. + +As always we are going to define an interface first, I am going to call it EasyCache and place it in the `Service/Cache` +namespace. I will require only one method which is base on the Symfony Cache Contract, and hast a key, a callback, and +the duration that the item should be cached as arguments. + +```php +cache->get( + $key, + fn () => $this->repo->all(), + 300 + ); + } + + public function byName(string $name): MarkdownPage + { + $key = base64_encode(self::class . 'byName' . $name); + return $this->cache->get( + $key, + fn () => $this->repo->byName($name), + 300 + ); + } +} +``` + +This simple wrapper just requires an EasyCache implementation and a MarkdownPageRepo in the constructor and uses them +to cache all queries for 5 minutes. The beauty is that we are not dependent on any implementation here, so we can switch +out the Repository or the Cache at any point down the road if we want to. + +In order to use that we need to update our `config/dependencies.php` to add an alias for the EasyCache interface as well +as defining our CachedMarkdownPageRepo as implementation for the MarkdownPageRepo interface: + +```php +MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, +EasyCache::class => fn (ApcuCache $c) => $c, +``` + +If we try to access our webpage now, we are getting an error, as PHP-DI has detected a circular dependency that cannot +be autowired. + +The Problem is that our CachedMarkdownPageRepo ist defined as the implementation for the MarkdownPageRepo, but it also +requires that exact interface as a dependency. To resolve this issue we need to manually tell the container how to build +the CachedMarkdownPageRepo by adding another line to the `config/dependencies.php` file: + +```php +CachedMarkdownPageRepo::class => fn (EasyCache $c, FileSystemMarkdownPageRepo $r) => new CachedMarkdownPageRepo($c, $r), +``` + +Here we explicitly require the FileSystemMarkdownPageRepo and us that to create the CachedMarkdownPageRepo object. + +When you now navigate to the pages list or to a specific page the first load should take a while (because of our added delay) +but the following request should be answered blazingly fast. + +Before moving on to the next chapter we can take the caching approach even further, in the middleware chapter I talked +about a simple CachingMiddleware that caches all the GET-Request for some seconds, as they should not change that often, +and we can bypass most of our application logic if we just complelety cache away the responses our application generates, +and return them quite early in our Middleware-Pipeline befor the router gets called, or the invoker calls the action, +which itself uses some other services to fetch all the needed data. + +We will introduce a new `Middleware` namespace to place our `Cache.php` middleware: +```php +getMethod() !== 'GET') { + return $handler->handle($request); + } + $keyHash = base64_encode($request->getUri()->getPath()); + $result = $this->cache->get( + $keyHash, + fn () => Serializer::toString($handler->handle($request)), + 300 + ); + return Serializer::fromString($result); + } +} +``` + +The code is quite straight forward, but you might be confused by the Responseserializer I have added here, we need this +because the response body is a stream object, which doesnt always gets serialized correctly, therefore I use a class from +the laminas project to to all the heavy lifting for us. + +We need to add the now middleware to the `config/middlewares.php` file. + +```php +>](19-database.md) diff --git a/app/public/index.php b/app/public/index.php index d93da3a..32f5eb3 100644 --- a/app/public/index.php +++ b/app/public/index.php @@ -2,4 +2,4 @@ declare(strict_types=1); -require __DIR__ . '/../src/Bootstrap.php'; \ No newline at end of file +require __DIR__ . '/../src/Bootstrap.php'; diff --git a/app/src/Action/Page.php b/app/src/Action/Page.php index 4af45f0..96696e4 100644 --- a/app/src/Action/Page.php +++ b/app/src/Action/Page.php @@ -49,7 +49,7 @@ class Page $pages = array_map(function (MarkdownPage $page) { return [ 'id' => $page->id, - 'title' => $page->content, + 'title' => $page->title, ]; }, $this->repo->all()); diff --git a/app/src/Factory/DoctrineEm.php b/app/src/Factory/DoctrineEm.php deleted file mode 100644 index b0be39b..0000000 --- a/app/src/Factory/DoctrineEm.php +++ /dev/null @@ -1,32 +0,0 @@ -settings->doctrine['devMode']); - - $config->setMetadataDriverImpl( - new AttributeDriver( - $this->settings->doctrine['metadataDirs'] - ) - ); - - return EntityManager::create( - $this->settings->connection, - $config, - ); - } -} diff --git a/app/src/Middleware/Cache.php b/app/src/Middleware/Cache.php new file mode 100644 index 0000000..8460761 --- /dev/null +++ b/app/src/Middleware/Cache.php @@ -0,0 +1,38 @@ +getMethod() !== 'GET') { + return $handler->handle($request); + } + $keyHash = base64_encode($request->getUri()->getPath()); + $result = $this->cache->get( + $keyHash, + fn () => $this->serializer::toString($handler->handle($request)), + 300 + ); + assert(is_string($result)); + return $this->serializer::fromString($result); + } +} diff --git a/app/src/Repository/CachedMarkdownPageRepo.php b/app/src/Repository/CachedMarkdownPageRepo.php new file mode 100644 index 0000000..a4d9180 --- /dev/null +++ b/app/src/Repository/CachedMarkdownPageRepo.php @@ -0,0 +1,49 @@ +cache->get( + $key, + fn () => $this->repo->all(), + 300 + ); + assert(is_array($result)); + foreach ($result as $page) { + assert($page instanceof MarkdownPage); + } + return $result; + } + + public function byName(string $name): MarkdownPage + { + $key = base64_encode(self::class . 'byName' . $name); + $result = $this->cache->get( + $key, + fn () => $this->repo->byName($name), + 300 + ); + assert($result instanceof MarkdownPage); + return $result; + } +} diff --git a/app/src/Service/Cache/ApcuCache.php b/app/src/Service/Cache/ApcuCache.php new file mode 100644 index 0000000..4835098 --- /dev/null +++ b/app/src/Service/Cache/ApcuCache.php @@ -0,0 +1,21 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config' + ]) + ); \ No newline at end of file diff --git a/implementation/18-caching/.phpcs.xml.dist b/implementation/18-caching/.phpcs.xml.dist new file mode 100644 index 0000000..3b433f6 --- /dev/null +++ b/implementation/18-caching/.phpcs.xml.dist @@ -0,0 +1,9 @@ + + + + + src + config + + + \ No newline at end of file diff --git a/implementation/18-caching/cli-config.php b/implementation/18-caching/cli-config.php new file mode 100644 index 0000000..fbc6598 --- /dev/null +++ b/implementation/18-caching/cli-config.php @@ -0,0 +1,13 @@ +getContainer(); + +return ConsoleRunner::createHelperSet($container->get(EntityManagerInterface::class)); diff --git a/implementation/18-caching/composer.json b/implementation/18-caching/composer.json new file mode 100644 index 0000000..29695da --- /dev/null +++ b/implementation/18-caching/composer.json @@ -0,0 +1,57 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3", + "mustache/mustache": "^2.14", + "psr/http-server-middleware": "^1.0", + "middlewares/trailing-slash": "^2.0", + "middlewares/whoops": "^2.0", + "erusev/parsedown": "^1.7", + "league/commonmark": "^2.2", + "ext-apcu": "*", + "ext-zend-opcache": "*" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require-dev": { + "phpstan/phpstan": "^1.5", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0", + "squizlabs/php_codesniffer": "^3.6", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.1", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "mnapoli/hard-mode": "^0.3.0" + }, + "config": { + "optimize-autoloader": true, + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1234 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + } +} diff --git a/implementation/18-caching/composer.lock b/implementation/18-caching/composer.lock new file mode 100644 index 0000000..40cd7d3 --- /dev/null +++ b/implementation/18-caching/composer.lock @@ -0,0 +1,2440 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "9a29468fd456190a9fbcff98ed42d862", + "packages": [ + { + "name": "dflydev/dot-access-data", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^3.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" + }, + "time": "2021-08-13T13:06:58+00:00" + }, + { + "name": "erusev/parsedown", + "version": "1.7.4", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "support": { + "issues": "https://github.com/erusev/parsedown/issues", + "source": "https://github.com/erusev/parsedown/tree/1.7.x" + }, + "time": "2019-12-30T22:54:17+00:00" + }, + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-03-29T20:12:16+00:00" + }, + { + "name": "league/commonmark", + "version": "2.2.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", + "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.15" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.30.0", + "commonmark/commonmark.js": "0.30.0", + "composer/package-versions-deprecated": "^1.8", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4", + "phpstan/phpstan": "^0.12.88 || ^1.0.0", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2022-02-26T21:24:45+00:00" + }, + { + "name": "league/config", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.90", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2021-08-14T12:15:32+00:00" + }, + { + "name": "middlewares/trailing-slash", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/middlewares/trailing-slash.git", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", + "shasum": "" + }, + "require": { + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to normalize the trailing slash of the uri path", + "homepage": "https://github.com/middlewares/trailing-slash", + "keywords": [ + "http", + "middleware", + "normalize", + "path", + "psr-15", + "psr-7", + "slash" + ], + "support": { + "issues": "https://github.com/middlewares/trailing-slash/issues", + "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" + }, + "time": "2020-12-02T00:06:55+00:00" + }, + { + "name": "middlewares/utils", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/middlewares/utils.git", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v2.16", + "guzzlehttp/psr7": "^2.0", + "laminas/laminas-diactoros": "^2.4", + "nyholm/psr7": "^1.0", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "slim/psr7": "^1.4", + "squizlabs/php_codesniffer": "^3.5", + "sunrise/http-message": "^1.0", + "sunrise/http-server-request": "^1.0", + "sunrise/stream": "^1.0.15", + "sunrise/uri": "^1.0.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\Utils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Common utils for PSR-15 middleware packages", + "homepage": "https://github.com/middlewares/utils", + "keywords": [ + "PSR-11", + "http", + "middleware", + "psr-15", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/middlewares/utils/issues", + "source": "https://github.com/middlewares/utils/tree/v3.3.0" + }, + "time": "2021-07-04T17:56:23+00:00" + }, + { + "name": "middlewares/whoops", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/middlewares/whoops.git", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.5", + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^5.0 || ^7.0", + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to use Whoops as error handler", + "homepage": "https://github.com/middlewares/whoops", + "keywords": [ + "error", + "http", + "middleware", + "psr-15", + "psr-7", + "server", + "whoops" + ], + "support": { + "issues": "https://github.com/middlewares/whoops/issues", + "source": "https://github.com/middlewares/whoops/tree/v2.0.2" + }, + "time": "2022-01-27T20:31:30+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nette/schema", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", + "shasum": "" + }, + "require": { + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": ">=7.1 <8.2" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "phpstan/phpstan-nette": "^0.12", + "tracy/tracy": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.2.2" + }, + "time": "2021-10-15T11:40:02+00:00" + }, + { + "name": "nette/utils", + "version": "v3.2.7", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", + "shasum": "" + }, + "require": { + "php": ">=7.2 <8.2" + }, + "conflict": { + "nette/di": "<3.0.6" + }, + "require-dev": { + "nette/tester": "~2.0", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v3.2.7" + }, + "time": "2022-01-24T11:29:14+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/master" + }, + "time": "2018-10-30T17:12:04+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "shasum": "" + }, + "require": { + "php": ">=8.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:55:41+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-04T08:16:47+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "doctrine/coding-standard", + "version": "8.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/coding-standard.git", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Müller", + "email": "st.mueller@dzh-online.de" + } + ], + "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "doctrine", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/doctrine/coding-standard/issues", + "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" + }, + "time": "2021-04-03T10:54:55+00:00" + }, + { + "name": "mnapoli/hard-mode", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/hard-mode.git", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", + "shasum": "" + }, + "require": { + "doctrine/coding-standard": "^8.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Hard mode for PHP", + "support": { + "issues": "https://github.com/mnapoli/hard-mode/issues", + "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" + }, + "time": "2020-10-12T07:54:37+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.5.4", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", + "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.5.4" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-04-03T12:39:00+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^1.2.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" + }, + "time": "2021-11-18T09:30:29+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1", + "ext-apcu": "*", + "ext-zend-opcache": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/18-caching/config/dependencies.php b/implementation/18-caching/config/dependencies.php new file mode 100644 index 0000000..df815c6 --- /dev/null +++ b/implementation/18-caching/config/dependencies.php @@ -0,0 +1,58 @@ + fn (SystemClockNow $n) => $n, + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, + Emitter::class => fn (BasicEmitter $e) => $e, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, + MarkdownParser::class => fn (ParsedownParser $p) => $p, + MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, + EasyCache::class => fn (ApcuCache $c) => $c, + CachedMarkdownPageRepo::class => fn (EasyCache $c, FileSystemMarkdownPageRepo $r) => new CachedMarkdownPageRepo($c, $r), + + + // Factories + ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), + Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), +]; diff --git a/implementation/18-caching/config/middlewares.php b/implementation/18-caching/config/middlewares.php new file mode 100644 index 0000000..ab662be --- /dev/null +++ b/implementation/18-caching/config/middlewares.php @@ -0,0 +1,13 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/page', [Page::class, 'list']); + $r->addRoute('GET', '/page/{page}', [Page::class, 'show']); + $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); + $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/implementation/18-caching/config/settings.php b/implementation/18-caching/config/settings.php new file mode 100644 index 0000000..c654565 --- /dev/null +++ b/implementation/18-caching/config/settings.php @@ -0,0 +1,12 @@ +>](02-composer.md) + +### Front Controller + +A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. + +To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. + +A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. + +The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. + +But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. + +So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. + +Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: + +```php +>](02-composer.md) diff --git a/implementation/18-caching/data/pages/02-composer.md b/implementation/18-caching/data/pages/02-composer.md new file mode 100644 index 0000000..a25a4a8 --- /dev/null +++ b/implementation/18-caching/data/pages/02-composer.md @@ -0,0 +1,75 @@ +[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) + +### Composer + +[Composer](https://getcomposer.org/) is a dependency manager for PHP. + +Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do +something. With Composer, you can install third-party libraries for your application. + +If you don't have Composer installed already, head over to the website and install it. You can find Composer packages +for your project on [Packagist](https://packagist.org/). + +Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will +be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. + +Add the following content to the file: + +```json +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubiana", + "email": "lubiana@hannover.ccc.de" + } + ] +} +``` + +In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use +whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. +Just replace it with your namespace in your own code. + +I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. + +As the Bootstrap.php file is placed in that directory we should +add the namespace to the File as well. Here is my current Bootstrap.php +as a reference: + +```php +>](03-error-handler.md) diff --git a/implementation/18-caching/data/pages/03-error-handler.md b/implementation/18-caching/data/pages/03-error-handler.md new file mode 100644 index 0000000..60465d0 --- /dev/null +++ b/implementation/18-caching/data/pages/03-error-handler.md @@ -0,0 +1,79 @@ +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) + +### Error Handler + +An error handler allows you to customize what happens if your code results in an error. + +A nice error page with a lot of information for debugging goes a long way during development. So the first package +for your application will take care of that. + +I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. +If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, +you have total control over your project. + +An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) + +To install a new package, open up your `composer.json` and add the package to the require part. It should now look +like this: + +```php +"require": { + "php": ">=8.1.0", + "filp/whoops": "^2.14" +}, +``` + +Now run `composer update` in your console and it will be installed. + +Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, +i that case composer automatically installs the package and updates your composer.json-file. + +But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, +ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you +only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. + +**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message +can help someone to gain access to your system. Always show a user friendly error page instead and send an email to +yourself, write to a log or something similar. So only you can see the errors in the production environment. + +For development that does not make sense though -- you want a nice error page. The solution is to have an environment +switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in +case no environment has been set. + +Then after the error handler registration, throw an `Exception` to test if everything is working correctly. +Your `Bootstrap.php` should now look similar to this: + +```php +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (\Throwable $e) { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +throw new \Exception("Ooooopsie"); + +``` + +You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until +you get it working. Now would also be a good time for another commit. + + +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/implementation/18-caching/data/pages/04-development-helpers.md b/implementation/18-caching/data/pages/04-development-helpers.md new file mode 100644 index 0000000..74f913c --- /dev/null +++ b/implementation/18-caching/data/pages/04-development-helpers.md @@ -0,0 +1,260 @@ +[<< previous](03-error-handler.md) | [next >>](05-http.md) + +### Development Helpers + +I have added some more helpers to my composer.json that help me with development. As these are scripts and programms +used only for development they should not be used in a production environment. Composer has a specific sections in its +file called "dev-dependencies", everything that is required in this section does not get installen in production. + +Let's install our dev-helpers and i will explain them one by one: +`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` + +#### Static Code Analysis with phpstan + +Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or +create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements +that are not (or not always) available, if-statements that always are true and a lot of other stuff. + +A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. + +```php +/** + * @param \DateTime $date + * @return void + */ +function printDate($date) { + $date->format('Y-m-d H:i:s'); +} + +printDate('now'); +``` +if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` + +It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has +no use, and secondly, that we are calling the function with a string instead of a datetime object. + +```shell +1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + ------ --------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ --------------------------------------------------------------------------------------------- +30 Call to method DateTime::format() on a separate line has no effect. +33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. + ------ --------------------------------------------------------------------------------------------- +``` + +The second error is something that "declare strict-types" already catches for us, but the first error is something that +we usually would not discover easily without speccially looking for this errortype. + +We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and +path everytime we want to check our code for errors: + +```yaml +parameters: + level: max + paths: + - src +``` +now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project + +With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us +some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. + +```shell +composer require --dev phpstan/extension-installer +composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules +``` + +During the first install you need to allow the extension installer to actually install the extension. The second command +installs some more strict rulesets and activates them in phpstan. + +If we now rerun phpstan it already tells us about some errors we have made: + +``` + ------ ----------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ ----------------------------------------------------------------------------------------------- +10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider + using long ternary. +25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: + http://bit.ly/subtypeexception +26 Unreachable statement - code above always terminates. + ------ ----------------------------------------------------------------------------------------------- +``` + +The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove +that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific +problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working +on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages +everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we +are adding to our code. + +In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the +phpstan.neon file and run phpstan with the +'--generate-baseline' option: + +```yaml +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` +```shell +[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline +Note: Using configuration file /home/vagrant/app/phpstan.neon. + 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + + + [OK] Baseline generated with 1 error. + + +``` + +you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) + +#### PHP-CS-Fixer + +Another great tool is the php-cs-fixer, which just applies a specific style to your code. + +when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current +directory. + +You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) + +personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup +a configuration file that i use in all my projects .php-cs-fixer.php: + +```php +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + ]) + ); +``` + +#### PHP Codesniffer + +The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra +rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. + +it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. + +Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have +guessed we are adding some extra rules. + +Lets install the ruleset with composer +```shell +composer require --dev mnapoli/hard-mode +``` + +and add a configuration file to actually use it '.phpcs.xml.dist' +```xml + + + + + src + + + +``` + +running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. + +``` +[vagrant@archlinux app]$ ./vendor/bin/phpcs + +FILE: src/Bootstrap.php +---------------------------------------------------------------------------------------------------- +FOUND 4 ERRORS AFFECTING 4 LINES +---------------------------------------------------------------------------------------------------- + 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. + 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead +---------------------------------------------------------------------------------------------------- +PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY +---------------------------------------------------------------------------------------------------- + +Time: 639ms; Memory: 10MB +``` + +You can then use `./vendor/bin/phpcbf` to try to fix them + + +#### Symfony Var-Dumper + +another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small +functions. + +dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects +or arrays. + +dd() on the other hand is a function that dumps its parameters and then exits the php-script. + +you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. + +#### Composer scripts + +now we have a few commands that are available on the command line. i personally do not like to type complex commands +with lots of parameters by hand all the time, so i added a few lines to my composer.json: + +```json +"scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" +}, +``` + +that way i can just type "composer" followed by the command name in the root of my project. if i want to start the +php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the +time. + +You could also configure PhpStorm to automatically run these commands in the background and highlight the violations +directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my +flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. + +My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before +commiting and pushing the code. + +[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/implementation/18-caching/data/pages/05-http.md b/implementation/18-caching/data/pages/05-http.md new file mode 100644 index 0000000..6166214 --- /dev/null +++ b/implementation/18-caching/data/pages/05-http.md @@ -0,0 +1,124 @@ +[<< previous](04-development-helpers.md) | [next >>](06-router.md) + +### HTTP + +PHP already has a few things built in to make working with HTTP easier. For example there are the +[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. + +These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, +if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, +then you will want a class with a nice object-oriented interface that you can use in your application instead. + +Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The +standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php +projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. + +As this is a widely adopted standard there are already several implementations available for us to use. I will choose +the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. + +Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a +[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. + +Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use +that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding +the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). + + +to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit +enter + +Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): + +```php +$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); +$response = new \Laminas\Diactoros\Response; +$response->getBody()->write('Hello World! '); +$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); +``` + +This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. + +In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the +write()-Method on that object. + + +To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: + +```php +echo $response->getBody(); +``` + +This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only +stores data. + +You can play around with the other methods of the Request object and take a look at its content with the dd() function. + +```php +dd($response) +``` + +Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot +be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which +creates a copy of the request object with the changed property and returns that clone: + +```php +$response = $response->withStatus(200); +$response = $response->withAddedHeader('Content-type', 'application/json'); +``` + +If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been +defined this way. + +But if you have been keeping attention you might argue that the following line should not work if the request object is +immutable. + +```php +$response->getBody()->write('Hello World!'); +``` + +The response-body implements a stream interface which is immutable for some reasons that are described in the +[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be +aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in +the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. +I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content +to a response object. + +```php +$body = $response->getBody(); +$body->write('Hello World!'); +$response = $response->withBody($body); +``` + +Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our +output-logic a little bit more. Replace the line that echos the response-body with the following: + +```php +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); +``` + +This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a +webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. + +Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. + +Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter + + +[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/implementation/18-caching/data/pages/06-router.md b/implementation/18-caching/data/pages/06-router.md new file mode 100644 index 0000000..6c39ae5 --- /dev/null +++ b/implementation/18-caching/data/pages/06-router.md @@ -0,0 +1,101 @@ +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) + +### Router + +A router dispatches to different handlers depending on rules that you have set up. + +With your current setup it does not matter what URL is used to access the application, it will always result in the same +response. So let's fix that now. + +I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own +favorite package. + +Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) + +By now you know how to install Composer packages, so I will leave that to you. + +Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. + +```php +$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +switch ($routeInfo[0]) { + case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: + $response = (new \Laminas\Diactoros\Response)->withStatus(405); + $response->getBody()->write('Method not allowed'); + $response = $response->withStatus(405); + break; + case \FastRoute\Dispatcher::FOUND: + $handler = $routeInfo[1]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + /** @var \Psr\Http\Message\ResponseInterface $response */ + $response = call_user_func($handler, $request); + break; + case \FastRoute\Dispatcher::NOT_FOUND: + default: + $response = (new \Laminas\Diactoros\Response)->withStatus(404); + $response->getBody()->write('Not Found!'); + break; +} +``` + +In the first part of the code, you are registering the available routes for your application. In the second part, the +dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, +we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. +If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. + +This setup might work for really small applications, but once you start adding a few routes your bootstrap file will +quickly get cluttered. So let's move them out into a separate file. + +Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; + +```php +addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}; +``` + +Now let's rewrite the route dispatcher part to use the `Routes.php` file. + +```php +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); +``` + +This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. + +Of course we now need to add the 'config' folder to the configuration files of our +devhelpers so that they can scan that directory as well. + +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/implementation/18-caching/data/pages/07-dispatching-to-a-class.md b/implementation/18-caching/data/pages/07-dispatching-to-a-class.md new file mode 100644 index 0000000..0c961a4 --- /dev/null +++ b/implementation/18-caching/data/pages/07-dispatching-to-a-class.md @@ -0,0 +1,137 @@ +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) + +### Dispatching to a Class + +In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). +MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to +learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) +and the followup posts. + +So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). + +We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other +common names are 'Controllers' or 'Actions'. + +Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. +In there, create a `Hello.php` file. + +```php +getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + } +} +``` + +You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) +that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is +fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement +it in some other parts of our application as well. In order to use that Interface we have to require it with composer: +'composer require psr/http-server-handler'. + +The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. +At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. + +Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: + +```php +return function(\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); + $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); +}; +``` + +Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared +the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing +the response to the one we defined for the second route. + +To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: + +```php +case \FastRoute\Dispatcher::FOUND: + $handler = new $routeInfo[1]; + if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { + throw new \Exception('Invalid Requesthandler'); + } + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + assert($response instanceof \Psr\Http\Message\ResponseInterface) + break; +``` + +So instead of just calling a method you are now instantiating an object and then calling the method on it. + +Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. + +And of course don't forget to commit your changes. + +Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still +generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for +example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still +have our whoopsie error-handler but i like to be more explicit in my control flow. + +In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new +Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and +InternalServerError. All three should extend phps Base Exception class. + +Here is my NotFound.php for example. + +```php + $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody()->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} +``` + +Check if our code still works, try to trigger some errors, run phpstan and the fix command +and don't forget to commit your changes. + +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/implementation/18-caching/data/pages/08-inversion-of-control.md b/implementation/18-caching/data/pages/08-inversion-of-control.md new file mode 100644 index 0000000..21f4f23 --- /dev/null +++ b/implementation/18-caching/data/pages/08-inversion-of-control.md @@ -0,0 +1,54 @@ +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) + +### Inversion of Control + +In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we +want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then +we would need to edit every one of our request handlers to call a different constructor of the class. + +The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that +instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done +with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). + +If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is +implemented, it will make sense. + +Change your `Hello` action to the following: + +```php +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: + +```php +$handler = new $className($response); +``` + +Of course we need to also update all the other handlers. + +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/implementation/18-caching/data/pages/09-dependency-injector.md b/implementation/18-caching/data/pages/09-dependency-injector.md new file mode 100644 index 0000000..7f7c6a2 --- /dev/null +++ b/implementation/18-caching/data/pages/09-dependency-injector.md @@ -0,0 +1,213 @@ +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) + +### Dependency Injector + +A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when +the class is instantiated. + +Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work +with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look +for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). + +I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) +out of the box. + +After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: + +```php +addDefinitions([ + \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), +]); + +return $builder->build(); +``` + +In this file we create a containerbuilder, add some definitions to it and return the container. +As the container supports autowiring we only need to define services where we want to use a specific implementation of +an interface. + +In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to +tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second +exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. + +Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), +as we will use that extensively. + +Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally +to create our Handler-Object + +```php +$container = require __DIR__ . '/../config/dependencies.php'; +assert($container instanceof \Psr\Container\ContainerInterface); + +$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); +assert($request instanceof \Psr\Http\Message\ServerRequestInterface); +``` + +The other part that has to be changed is the dispatching of the route. Before you had the following code: + +```php +$className = $routeInfo[1]; +$handler = new $className($response); +assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Change that to the following: + +```php +/** @var RequestHandlerInterface $handler */ +$className = $routeInfo[1]; +$handler = $container->get($className); +assert($handler instanceof RequestHandlerInterface); +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Make sure to use the container fetch the response object in the catch blocks as well: + +```php +} catch (MethodNotAllowed) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(404); + $response->getBody()->write('Not Found'); +} +``` + +Now all your controller constructor dependencies will be automatically resolved with PHP-DI. + +We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons +in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call +`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we +want to work with a different date in a test. + +Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation +that creates us a DateTimeImmutable object with the current date and time. + +src/Service/Time/Now.php: +```php +namespace Lubian\NoFramework\Service\Time; + +interface Now +{ + public function __invoke(): \DateTimeImmutable; +} +``` +src/Service/Time/SystemClockNow.php: +```php +namespace Lubian\NoFramework\Service\Time; + +final class SystemClockNow implements Now +{ + + public function __invoke(): \DateTimeImmutable + { + return new \DateTimeImmutable; + } +} +``` +If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and +update the handle-method to use the new class property: + +```php +getAttribute('name', 'Stranger'); + $nowAsString = ($this->now)()->format('H:i:s'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowAsString); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI +automatically figures out what classes are requested in the constructor and tries to create the objects needed. + +But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred +S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: + +```php + public function __construct( + private ResponseInterface $response, + private Now $now, + ) +``` + +When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation +should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: + +```php +\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), +``` + +we could also use the PHP-DI create method to delegate the object creation to the container implementation: +```php +\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), +``` + +this way the container can try to resolve any dependencies that the class might have internally, but prefer the other +method because we are not depending on this specific dependency injection implementation. + +Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are +requesting the Hello action. + +If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As +we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way +we wont get any warnings for this particular issue, but for any other issues we add to our code. + +Update the phpstan.neon file to include a "baseline" file: + +``` +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` + +if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and +ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just +'baseline' + +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/implementation/18-caching/data/pages/10-invoker.md b/implementation/18-caching/data/pages/10-invoker.md new file mode 100644 index 0000000..3033fae --- /dev/null +++ b/implementation/18-caching/data/pages/10-invoker.md @@ -0,0 +1,102 @@ +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) + +### Invoker + +Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the +one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long +with some services and not the whole Requestobject with all its various properties. + +If we take our Hello action for example we only need a response object, the time service and the 'name' information from +the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named +the class hello and it would be redundant to also call the the method hello. So an updated version of that class could +look like this: + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + string $name = 'Stranger', + ): ResponseInterface + { + $body = $this->response->getBody(); + $nowString = $now->get()->format('H:i:s'); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowString); + return $response + ->withBody($body) + ->withStatus(200); + } +} +``` + +It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short +closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the +rootpath of our application yet. + +```php +$r->addRoute('GET', '/hello[/{name}]', Hello::class); +$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); +$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +``` + +In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the +route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that +class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what +arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router +if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple +callable we would need to manually use reflection as well to resolve all the arguments. + +But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) +which handles all of that for us. + +After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo +switch section in the Bootstrap.php file to use the container->call() method: + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2]; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$response = $container->call($handler, $args); +``` + +Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. + +But by now you should know that I do not like to depend on specific implementations and the call method is not defined in +the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container +or any other implementation. + +Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific +container implementation so we could use it with any container that implements the ContainerInterface. And best of all +the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that +we could implement if we ever want to write our own implementation or we could write an adapter that uses a different +class that solves the same problem. + +But for now we are using the solution provided by PHP-DI. +So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2] ?? []; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$invoker = $container->get(InvokerInterface::class); +assert($invoker instanceof InvokerInterface); +$response = $invoker->call($handler, $args); +assert($response instanceof ResponseInterface); +``` + +Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) +by php, and even some more. + +But let us move on to something more fun and add some templating functionality to our application as we are trying to build +a website in the end. + +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/implementation/18-caching/data/pages/11-templating.md b/implementation/18-caching/data/pages/11-templating.md new file mode 100644 index 0000000..7bfe1aa --- /dev/null +++ b/implementation/18-caching/data/pages/11-templating.md @@ -0,0 +1,236 @@ +[<< previous](10-invoker.md) | [next >>](12-configuration.md) + +### Templating + +A template engine is not necessary with PHP because the language itself can take care of that. But it can make things +like escaping values easier. They also make it easier to draw a clear line between your application logic and the +template files which should only put your variables into the HTML code. + +A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please +also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. +Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. + +For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install +that package before you continue (`composer require mustache/mustache`). + +Another well known alternative would be [Twig](http://twig.sensiolabs.org/). + +Now please go and have a look at the source code of the +[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class +does not implement an interface. + +You could just type hint against the concrete class. But the problem with this approach is that you create tight +coupling. + +In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the +implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want +to add functionality to the engine. You can't do that without going back and changing all your code that is tightly +coupled. + +What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need +another implementation, you just implement that interface in your new class and inject the new class instead. + +Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). +This sounds a lot more complicated than it is, so just follow along. + +First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). +This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. +A class can implement multiple interfaces if necessary. + +So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a +new folder in your `src/` folder with the name `Template` where you can put all the template related things. + +In there create a new interface `Renderer.php` that looks like this: + +```php + $data */ + public function render(string $template, array $data = []) : string; +} +``` + +Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file +`MustacheRenderer.php` with the following content: + +```php +engine->render($template, $data); + } +} +``` + +As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple +and only fulfills the interface. + +Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know +which implementation he has to inject when you hint for the interface. Add this line: + +```php +[ + ... + \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) + ->constructor(new Mustache_Engine), +] +``` + +Now update the Hello.php class to require an implementation of our renderer interface +and use that to render a string using mustache syntax. + + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + Renderer $renderer, + string $name = 'Stranger', + ): ResponseInterface { + $body = $response->getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render( + 'Hello {{name}}, the time is {{now}}!', + $data, + ); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} +``` + +Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. +But what we want is template files, so let's go back and change that. + +To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the +`dependencies.php` file and add the following code: + +```php +[ + ... + Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), + Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), +] +``` + +We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. +Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have +to rename all the template files. + +To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the +dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader +in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object +we can then use it in the factory. + +In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: + +``` +

Hello World

+Hello {{ name }} +``` + +Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` + +Navigate to the hello page in your browser to make sure everything works. + +One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies +file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration +values. + +Lets create a 'Settings' class in our './src' Folder: + +```php +addDefinitions([ + Settings::class => fn () => require __DIR__ '/settings.php', + ResponseInterface::class => create(Response::class), + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + Renderer::class => fn (ME $me) => new Mustache($me), + MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), +]); + +return $builder->build(); +``` + + + +And as always, don't forget to commit your changes. + + +[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/implementation/18-caching/data/pages/12-configuration.md b/implementation/18-caching/data/pages/12-configuration.md new file mode 100644 index 0000000..4b60c19 --- /dev/null +++ b/implementation/18-caching/data/pages/12-configuration.md @@ -0,0 +1,200 @@ +[<< previous](11-templating.md) | [next >>](13-refactoring.md) + +### Configuration + +In the last chapter we added some more definitions to our dependencies.php in that definitions +we needed to pass quite a few configuration settings and filesystem strings to the constructors +of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. + +As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. + +As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. + +Lets create a 'Settings' class in our './src' Folder: + +```php +filePath; + } +} +``` + +If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files +and craft a settings object from them. + +As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another +factory for our Container because everyone should have a Friend :) + +```php +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn () => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} +``` + +For this to work we need to change our dependencies.php file to just return the array of definitions: +And here we can instantly use the Settings object to create our template engine. + +```php + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), +]; +``` + +Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: + +```php +... +error_reporting(E_ALL); + +$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); +$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); +... +``` + +Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. + +[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/implementation/18-caching/data/pages/13-refactoring.md b/implementation/18-caching/data/pages/13-refactoring.md new file mode 100644 index 0000000..6dbbb8d --- /dev/null +++ b/implementation/18-caching/data/pages/13-refactoring.md @@ -0,0 +1,373 @@ +[<< previous](12-configuration.md) | [next >>](14-middleware.md) + +### Refactoring + +By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no +reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. +After all the bootstrap file should just set up the classes needed for the handling logic and execute them. + +At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. +As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the +method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non +static and extracted an interface. + +'./src/Http/Emitter.php' +```php +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + echo $response->getBody(); + } +} +``` +After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following +code in the Bootstrap.php to emit the response: + +```php +/** @var Emitter $emitter */ +$emitter = $container->get(Emitter::class); +$emitter->emit($response); +``` + +If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily +write an adapter that implements your emitter interface and wraps that more advanced emitter + +Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and +calling the routerhandler that in the passes the request to a function and gets the response. + +For this to steps to be seperated we are going to create two more classes: +1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object +2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the + requestobject, fetches the correct object from the container and calls it to create a response. + +Lets create the HandlerInterface first: + +```php +getAttribute($this->routeAttributeName, false); + assert($handler !== false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} + +``` + +We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. +The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler +as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in +the next chapter. + +```php +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} +``` + +Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. +```php +[ + '...', + Emitter::class => fn (BasicEmitter $e) => $e, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, +], +``` + +And then we can update our Bootstrap.php to fetch all the services and let them handle the request. + +```php +... +$routeMiddleWare = $container->get(MiddlewareInterface::class); +assert($routeMiddleWare instanceof MiddlewareInterface); +$handler = $container->get(RoutedRequestHandler::class); +assert($handler instanceof RequestHandlerInterface); +$emitter = $container->get(Emitter::class); +assert($emitter instanceof Emitter); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$response = $routeMiddleWare->process($request, $handler); +$emitter->emit($response); +``` +Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of +code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). + +So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. + +I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class +should require to function properly. + +* A RequestFactory + We want our Kernel to be able to build the request itself +* An Emitter + Without an Emitter we will not be able to send the response to the client +* RouteMiddleware + To decore the request with the correct handler for the requested route +* RequestHandler + To delegate the request to the correct funtion that creates the response + +As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface +to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our +interface: + +```php +factory::fromGlobals(); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} +``` + +For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: + +```php +routeMiddleware->process($request, $this->handler); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} + +``` + +We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines + +```php +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); +``` + +You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking +at the Whoops output and adding the needed definitions to the dependencies.php file. + +And as always, don't forget to commit your changes. + +[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/implementation/18-caching/data/pages/14-middleware.md b/implementation/18-caching/data/pages/14-middleware.md new file mode 100644 index 0000000..81f82a5 --- /dev/null +++ b/implementation/18-caching/data/pages/14-middleware.md @@ -0,0 +1,303 @@ +[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) + +### Middleware + +In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain +a bit more about what this interface does and why it is used in many applications. + +The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets +passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all +the middlewars to the client/emitter. + +So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the +response after it gets created by our handlers. + +So lets take a look at the middleware and the requesthandler interfaces + +```php +interface MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; +} + +interface RequestHandlerInterface +{ + public function handle(ServerRequestInterface $request): ResponseInterface; +} +``` + +The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a +requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the +response. + +But the middleware could just ignore the handler and produce a response on its own as the interface just requires us +to produce a response. + +A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users +that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the +database. + +In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates +the request with an 'isAuthenticated' attribute. + +If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that +response is not already cached, than we let the handler create the response and store that in the cache for a few +seconds + +```php +interface CacheInterface +{ + public function get(string $key, callable $resolver, int $ttl): mixed; +} +``` + +The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one +defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses +the callable to produce the value and stores it for the time specified in ttl. + +so lets write our caching middleware: + +```php +final class CachingMiddleware implements MiddlewareInterface +{ + public function __construct(private CacheInterface $cache){} + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { + $key = $request->getUri()->getPath(); + return $this->cache->get($key, fn() => $handler->handle($request), 10); + } + return $handler->handle($request); + } +} +``` + +we can also modify the response after it has been created by our application, for example we could implement a gzip +middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser +know that our application is used to serve dank memes: + +```php +final class DankMemeMiddleware implements MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $response = $handler->handle($request); + return $response->withAddedHeader('Meme', 'Dank'); + } +} +``` + +but for our application we are going to just add two external middlewares: + +* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. +* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware + +```bash +composer require middlewares/trailing-slash +composer require middlewares/whoops +``` + +The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the +application as well as the middleware stack. + +Our desired request -> response flow looks something like this: + + Client + | ^ + v | + Kernel + | ^ + v | + Whoops Middleware + | ^ + v | + TrailingSlash + | ^ + v | + Routing + | ^ + v | + ContainerResolver + | ^ + v | + Controller/Action + +As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every +middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. + +```php +interface Pipeline +{ + public function dispatch(ServerRequestInterface $request): ResponseInterface; +} +``` + +And our implementation looks something like this: + +```php + $middlewares + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} +``` + +Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code +that should produce our response. + +In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument +and store that itself as the current tip. + +There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) + +Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline +Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline + +```php +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} +``` + +And configure the container to use the Factory to create the Pipeline: + +```php + ..., + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + ... +``` +And of course a new file called middlewares.php in our config folder: +```php +pipeline->dispatch($request); +} +``` + +Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our +pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a +dependency container, if you want you can do that as well. + +**A quick note about docblocks:** You might have noticed, that I rarely add docblocks to my the code in the examples, and +when I do it seems kind of random. My philosophy is that I only add docblocks when there is no way to automatically get +the exact type from the code itself. For me docblocks only serve two purposes: help my IDE to understand what it choices +it has for code completion and to help the static analysis to better understand the code. There is a great blogpost +about the [cost and value of DocBlocks](https://localheinz.com/blog/2018/05/06/cost-and-value-of-docblocks/), although it +is written in 2018 at a time before PHP 7.4 was around everything written there is still valid today. + +[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) diff --git a/implementation/18-caching/data/pages/15-adding-content.md b/implementation/18-caching/data/pages/15-adding-content.md new file mode 100644 index 0000000..64562fa --- /dev/null +++ b/implementation/18-caching/data/pages/15-adding-content.md @@ -0,0 +1,253 @@ +[<< previous](14-middleware.md) | [next >>](16-data-repository.md) + +### Adding Content + +By now we did not really display anything but some examples to in our application and it is now time to make our app +display some content. For example we could our app be able to display the Markdown files used in this tutorial as +nicely rendered HTML Pages that can be viewed in the browser instead of the editor you are using. + +So lets start by copying the markdown files to our app directory. I have created a new folder 'data/pages' and placed all +the markdown files in there. + +Next we need a markdown parser, a pretty simple one is [Parsedown](https://parsedown.org/), if you want more features +you could also use the [Commonmark parser](https://commonmark.thephpleague.com/). I will choose Parsedown here, but you +can use whatever you like. + +After installing Parsedown lets write a Markdownparser interface and an implementation using parsedown. + +We only need one function that receives a string of Markdown and returns the HTML represantion (as a string as well). + + + +```php +parser->parse($markdown); + } +} +``` + +We could now use the ParsedownRender class directly in our actions by typehinting the classname as an argument to the +constructor or a method, but as we always want to rely on an interface instead of an implementation we need to define +the the ParsedownRenderer as the correct implementation for the MarkdownRenderer interface in the dependencies file: + +```php +... + \Lubian\NoFramework\Template\MarkdownParser::class => fn(\Lubian\NoFramework\Template\ParsedownParser $p) => $p, +... +``` + +You can test that in our "Other.php" action and try out if the Parser works and is able to render Markdown to HTML: + +```php +public function someFunctionName(ResponseInterface $response, MarkdownParser $parser): ResponseInterface +{ + $html = $parser->parse('This *works* **too!**'); + $response->getBody()->write($html); + return $response->withStatus(200); +} +``` + +But we want to display complete Pages written in Markdown, it would also be neat to be able to display a list of all +available pages. For that we need a few things: + +Firstly we need two new Templates, one for the list of the Pages, and the second one for displaying a single pages +content. Create a new folder in `templates/page` with to files: + +`templates/page/list.html` +```html + + + + + Pages + + + +
+ +
+ + +``` + +This template iterates over a provided array of pages, each element consists of the two properties: an id and a title, +those are simply displayed using an unordered list. + +`templates/page/show.html` +```html + + + + + {{title}} + + + + + + +
+ {{{content}}} +
+ + +``` + +The second templates displays a single rendered markdown page. As data it expects the title and the content as array. +I used an extra bracket for the content ```{{{content}}}``` so that the Mustache-Renderer does not escape the provided +html and thereby destroys the the parsed markdown. + +You might have spotted that I added [Pico.css](https://picocss.com/) which is just a very small css framework to make the +pages a little bit nicer to look at. It mostly provides some typography styles that work great with rendered Markdown, +but you can leave that out or use any other css framework you like. There is also some Javascript that adds syntax +highlighting to the code. + +After you have taken care of the templating side we can now create an new Action class with two methods to display use +our markdown files and the templates to create the pages. As we have two templates I propose to use Two methods in our +Action: +`src/Action/Page.php` +```php +function show(string $name): \Psr\Http\Message\ResponseInterface; +function list(): \Psr\Http\Message\ResponseInterface; +``` + +Lets define two routes. `/page` should display the overview of all pages, and if the add the name of chapter to the +route, `/page/adding-content` for example, the show action should be called with the name as a variable: + +`config/routes.php` +```php +$r->addRoute('GET', '/page', [Page::class, 'list']); +$r->addRoute('GET', '/page/{page}', [Page::class, 'show']); +``` + +Here is my Implementation. If have added a little regex replacement in the show method that replaces the links to the +next and previous chapter so that it works with our routing configuration. + +`src/Action/Page.php` +```php +getPages(), + fn (string $filename) => str_contains($filename, $page) + ) + )[0]; + $markdown = file_get_contents($page); + + // fix the next and previous buttons to work with our routing + $markdown = preg_replace('/\(\d\d-/m', '(', $markdown); + $markdown = str_replace('.md)', ')', $markdown); + + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + $data = [ + 'title' => substr($page, 3), + 'content' => $this->parser->parse($markdown), + ]; + $html = $this->renderer->render('page/show', $data); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map(function (string $page) { + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + return [ + 'id' => substr($page, 0, 2), + 'title' => substr($page, 3), + ]; + }, $this->getPages()); + $html = $this->renderer->render('page/list', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } + + /** + * @return string[] + */ + private function getPages(): array + { + $files = glob($this->pagesPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return $files; + } +} +``` + +You can now navigate your Browser to [localhost:1234/page][http://localhost:1234/page] and try out if everything works. + +Of course this code is far from looking good. We heavily rely on the pages being files in the filesystem, and the action +should never be aware of the filesystem in the first place, also we have a lot of string replacements and other repetetive +code in the file. And phpstan is gonna scream at us a lot, but if we rewrite the code to satisfy all the checks we would +add even more lines to that simple class, so lets move on to the next chapter where we move all the logic to seperate +classes following our holy SOLID principles :) + + +[<< previous](14-middleware.md) | [next >>](16-data-repository.md) diff --git a/implementation/18-caching/data/pages/16-data-repository.md b/implementation/18-caching/data/pages/16-data-repository.md new file mode 100644 index 0000000..d9a3218 --- /dev/null +++ b/implementation/18-caching/data/pages/16-data-repository.md @@ -0,0 +1,265 @@ +[<< previous](15-adding-content.md) | [next >>](17-performance.md) + +## Data Repository + +At the end of the last chapter I mentioned being unhappy with our Pages action, because there is to much stuff happening +there. We are firstly receiving some Arguments, then we are using those to query the filesytem for the given page, +loading the specific file from the filesystem, rendering the markdown, passing the markdown to the template renderer, +adding the resulting html to the response and then returning the response. + +In order to make our pageaction independent from the filesystem and move the code that is responsible for reading the +files +to a better place I want to introduce +the [Repository Pattern](https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html). + +I want to start by creating a class that represents the Data that is included in a page so that. For now I can spot +three +distrinct attributes. + +* the ID (or chapternumber) +* the title (or name) +* the content + +Currently all those properties are always available, but we might later be able to create new pages and store them, but +at that point in time we are not yet aware of the new available ID, so we should leave that property nullable. This +allows +us to create an object without an id and let the code that actually saves the object to a persistant store define a +valid +id on saving. + +Lets create an new Namespace called `Model` and put a `MarkdownPage.php` class in there: + +```php +dataPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return array_map(function (string $filename) { + $content = file_get_contents($filename); + if ($content === false) { + throw new InternalServerError('cannot read pages'); + } + $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); + return new MarkdownPage( + (int) substr($idAndTitle, 0, 2), + substr($idAndTitle, 3), + $content + ); + }, $files); + } + + public function byName(string $name): MarkdownPage + { + $pages = array_values( + array_filter( + $this->all(), + fn (MarkdownPage $p) => $p->title === $name, + ) + ); + + if (count($pages) !== 1) { + throw new NotFound; + } + + return $pages[0]; + } +} +``` + +With that in place we need to add the required `$pagesPath` to our settings class and add specify that in our +configuration. + +`src/Settings.php` + +```php +final class Settings +{ + public function __construct( + public readonly string $environment, + public readonly string $dependenciesFile, + public readonly string $middlewaresFile, + public readonly string $templateDir, + public readonly string $templateExtension, + public readonly string $pagesPath, + ) { + } +} +``` + +`config/settings.php` + +```php +return new Settings( + environment: 'prod', + dependenciesFile: __DIR__ . '/dependencies.php', + middlewaresFile: __DIR__ . '/middlewares.php', + templateDir: __DIR__ . '/../templates', + templateExtension: '.html', + pagesPath: __DIR__ . '/../data/pages/', +); +``` + +Of course we need to define the correct implementation for the container to choose when we are requesting the Repository +interface: +`conf/dependencies.php` + +```php +MarkdownPageRepo::class => fn (FileSystemMarkdownPageRepo $r) => $r, +FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), +``` + +Now you can request the MarkdownPageRepo Interface in your page action and use the defined functions to get the +MarkdownPage +Objects. My `src/Action/Page.php` looks like this now: + +```php +repo->byName($page); + + // fix the next and previous buttons to work with our routing + $content = preg_replace('/\(\d\d-/m', '(', $page->content); + assert(is_string($content)); + $content = str_replace('.md)', ')', $content); + + $data = [ + 'title' => $page->title, + 'content' => $this->parser->parse($content), + ]; + + $html = $this->renderer->render('page/show', $data); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map(function (MarkdownPage $page) { + return [ + 'id' => $page->id, + 'title' => $page->content, + ]; + }, $this->repo->all()); + + $html = $this->renderer->render('page/list', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } +} +``` + +Check the page in your browser if everything still works, don't forget to run phpstan and the others fixers before +committing your changes and moving on to the next chapter. + +[<< previous](15-adding-content.md) | [next >>](17-performance.md) diff --git a/implementation/18-caching/data/pages/17-performance.md b/implementation/18-caching/data/pages/17-performance.md new file mode 100644 index 0000000..c83c7d5 --- /dev/null +++ b/implementation/18-caching/data/pages/17-performance.md @@ -0,0 +1,43 @@ +[<< previous](16-data-repository.md) | [next >>](18-caching.md) + +## Autoloading performance + +Although our application is still very small and you should not really experience any performance issues right now, +there are still some things we can already consider and take a look at. If I check the network tab in my browser it takes +about 90-400ms to show a simple rendered markdownpage, with is sort of ok but in my opinion way to long as we are not +really doing anything and do not connect to any external services. Mostly we are just reading around 16 markdown files, +a template, some config files here and there and parse some markdown. So that should not really take that long. + +The problem is, that we heavily rely on autoloading for all our class files, in the `src` folder. And there are also +quite a lot of other files in composers `vendor` directory. To understand while this is becomming we should make +ourselves familiar with how [autoloading in php](https://www.php.net/manual/en/language.oop5.autoload.php) works. + +The basic idea is, that every class that php encounters has to be loaded from somewhere in the filesystem, we could +just require the files manually but that is tedious, unflexible and can often cause errors. + +The problem we are now facing is that the composer autoloader has some rules to determine from where in the filesystem +a class definition might be placed, then the autoloader tries to locate a file by the namespace and classname and if it +exists includes that file. + +If we only have a handfull of classes that does not take a lot of time, but as we are growing with our application this +easily takes longer than necesery, but fortunately composer has some options to speed up the class loading. + +Take a few minutes to read the documentation about [composer autoloader optimization](https://getcomposer.org/doc/articles/autoloader-optimization.md) + +You can try all 3 levels of optimizations, but we are going to stick with the first one for now, so lets create an +optimized classmap. + +`composer dump-autoload -o` + +After composer has finished you can start the devserver again with `composer serve` and take a look at the network tab +in your browsers devtools. + +In my case the response time falls down to under an average of 30ms with some spikes in between, but all in all it looks really good. +You can also try out the different optimization levels and see if you can spot any differences. + +Although the composer manual states not to use the optimtization in a dev environment I personally have not encountered +any errors with the first level of optimizations, so we can use that level here. If you add the line from the documentation +to your `composer.json` so that the autoloader gets optimized everytime we install new packages. + + +[<< previous](16-data-repository.md) | [next >>](18-caching.md) diff --git a/implementation/18-caching/data/pages/18-caching.md b/implementation/18-caching/data/pages/18-caching.md new file mode 100644 index 0000000..42e9cb1 --- /dev/null +++ b/implementation/18-caching/data/pages/18-caching.md @@ -0,0 +1,252 @@ +[<< previous](17-performance.md) | [next >>](19-database.md) + +**DISClAIMER** I do not really have a lot of experience when it comes to caching, so this chapter is mostly some random +thoughts and ideas I wanted to explore when writing this tutorial, you should definitely take everything that is being +said here with caution and try to read up on some other sources. But that holds true for the whole tutorial anyway :) + +## Caching + +In the last chapter we greatly improved the perfomance for the lookup of all our classfiles, but currently we do not +have any real bottlenecks in our application like complex queries. + +But in a real application we are going to execute some really heavy and time intensive database queries that can take +quite a while to be completed. + +We can simulate that by adding a simple delay in our `FileSystemMarkdownPageRepo`. + +```php + return array_map(function (string $filename) { + usleep(rand(100, 400) * 1000); + $content = file_get_contents($filename); + if ($content === false) { + throw new InternalServerError('cannot read pages'); + } + $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); + return new MarkdownPage( + (int) substr($idAndTitle, 0, 2), + substr($idAndTitle, 3), + $content + ); +}); +``` + +Here I added a function that pauses the scripts execution for a random time between 100 and 400ms for every markdownpage +in every call of the `all()` method. + +If you open any page or even the listAction in you browser you will see, that it takes quite a time to render that page. +Although this is a silly example we do not really need to query the database on every request, so lets add a way to cache +the database results between requests. + +The PHP-Community has already adressed the issue of having easy to use access to cache libraries, there is the +[PSR-6 Caching Interface](https://www.php-fig.org/psr/psr-6) which gives us easy access to many different implementations, +then there is also a much simpler [PSR-16 Simple Cache](https://www.php-fig.org/psr/psr-16) which makes the use even more +easy, and most Caching Libraries implement Both interfaces anyway. You would think that this is more than enough solutions +to satisfy all the Caching needs around, but the Symfony People decided that Caching should be even simpler and easier +to use and defined their own [Interface](https://symfony.com/doc/current/components/cache.html#cache-component-contracts) +which only needs two methods. You should definitely take a look at the linked documentation as it really blew my mind +when I first encountered it. + +The basic idea is that you provide a callback that computes the requested value. The Cache implementation then checks +if it already has the value stored somewhere and if it doesnt it just executes the callback and stores the value for +future calls. + +It is really simple and great to use. In a real world application you should definitely use that or a PSR-16 implementation +but for this tutorial I wanted to roll out my own solution, so here we go. + +As always we are going to define an interface first, I am going to call it EasyCache and place it in the `Service/Cache` +namespace. I will require only one method which is base on the Symfony Cache Contract, and hast a key, a callback, and +the duration that the item should be cached as arguments. + +```php +cache->get( + $key, + fn () => $this->repo->all(), + 300 + ); + } + + public function byName(string $name): MarkdownPage + { + $key = base64_encode(self::class . 'byName' . $name); + return $this->cache->get( + $key, + fn () => $this->repo->byName($name), + 300 + ); + } +} +``` + +This simple wrapper just requires an EasyCache implementation and a MarkdownPageRepo in the constructor and uses them +to cache all queries for 5 minutes. The beauty is that we are not dependent on any implementation here, so we can switch +out the Repository or the Cache at any point down the road if we want to. + +In order to use that we need to update our `config/dependencies.php` to add an alias for the EasyCache interface as well +as defining our CachedMarkdownPageRepo as implementation for the MarkdownPageRepo interface: + +```php +MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, +EasyCache::class => fn (ApcuCache $c) => $c, +``` + +If we try to access our webpage now, we are getting an error, as PHP-DI has detected a circular dependency that cannot +be autowired. + +The Problem is that our CachedMarkdownPageRepo ist defined as the implementation for the MarkdownPageRepo, but it also +requires that exact interface as a dependency. To resolve this issue we need to manually tell the container how to build +the CachedMarkdownPageRepo by adding another line to the `config/dependencies.php` file: + +```php +CachedMarkdownPageRepo::class => fn (EasyCache $c, FileSystemMarkdownPageRepo $r) => new CachedMarkdownPageRepo($c, $r), +``` + +Here we explicitly require the FileSystemMarkdownPageRepo and us that to create the CachedMarkdownPageRepo object. + +When you now navigate to the pages list or to a specific page the first load should take a while (because of our added delay) +but the following request should be answered blazingly fast. + +Before moving on to the next chapter we can take the caching approach even further, in the middleware chapter I talked +about a simple CachingMiddleware that caches all the GET-Request for some seconds, as they should not change that often, +and we can bypass most of our application logic if we just complelety cache away the responses our application generates, +and return them quite early in our Middleware-Pipeline befor the router gets called, or the invoker calls the action, +which itself uses some other services to fetch all the needed data. + +We will introduce a new `Middleware` namespace to place our `Cache.php` middleware: +```php +getMethod() !== 'GET') { + return $handler->handle($request); + } + $keyHash = base64_encode($request->getUri()->getPath()); + $result = $this->cache->get( + $keyHash, + fn () => Serializer::toString($handler->handle($request)), + 300 + ); + return Serializer::fromString($result); + } +} +``` + +The code is quite straight forward, but you might be confused by the Responseserializer I have added here, we need this +because the response body is a stream object, which doesnt always gets serialized correctly, therefore I use a class from +the laminas project to to all the heavy lifting for us. + +We need to add the now middleware to the `config/middlewares.php` file. + +```php +>](19-database.md) diff --git a/implementation/18-caching/phpstan-baseline.neon b/implementation/18-caching/phpstan-baseline.neon new file mode 100644 index 0000000..61697a1 --- /dev/null +++ b/implementation/18-caching/phpstan-baseline.neon @@ -0,0 +1,7 @@ +parameters: + ignoreErrors: + - + message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" + count: 1 + path: src/Http/InvokerRoutedHandler.php + diff --git a/implementation/18-caching/phpstan.neon b/implementation/18-caching/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/18-caching/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/18-caching/public/css/spectre-exp.min.css b/implementation/18-caching/public/css/spectre-exp.min.css new file mode 100644 index 0000000..d313774 --- /dev/null +++ b/implementation/18-caching/public/css/spectre-exp.min.css @@ -0,0 +1 @@ +/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/implementation/18-caching/public/css/spectre-icons.min.css b/implementation/18-caching/public/css/spectre-icons.min.css new file mode 100644 index 0000000..0276f7b --- /dev/null +++ b/implementation/18-caching/public/css/spectre-icons.min.css @@ -0,0 +1 @@ +/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/implementation/18-caching/public/css/spectre.min.css b/implementation/18-caching/public/css/spectre.min.css new file mode 100644 index 0000000..0fe23d9 --- /dev/null +++ b/implementation/18-caching/public/css/spectre.min.css @@ -0,0 +1 @@ +/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/implementation/18-caching/public/favicon.ico b/implementation/18-caching/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/18-caching/public/index.php b/implementation/18-caching/public/index.php new file mode 100644 index 0000000..32f5eb3 --- /dev/null +++ b/implementation/18-caching/public/index.php @@ -0,0 +1,5 @@ +getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render('hello', $data); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/18-caching/src/Action/Other.php b/implementation/18-caching/src/Action/Other.php new file mode 100644 index 0000000..da9ceaf --- /dev/null +++ b/implementation/18-caching/src/Action/Other.php @@ -0,0 +1,16 @@ +parse('This *works* **too!**'); + $response->getBody()->write($html); + return $response->withStatus(200); + } +} diff --git a/implementation/18-caching/src/Action/Page.php b/implementation/18-caching/src/Action/Page.php new file mode 100644 index 0000000..96696e4 --- /dev/null +++ b/implementation/18-caching/src/Action/Page.php @@ -0,0 +1,60 @@ +repo->byName($page); + + // fix the next and previous buttons to work with our routing + $content = preg_replace('/\(\d\d-/m', '(', $page->content); + assert(is_string($content)); + $content = str_replace('.md)', ')', $content); + + $data = [ + 'title' => $page->title, + 'content' => $this->parser->parse($content), + ]; + + $html = $this->renderer->render('page/show', $data); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map(function (MarkdownPage $page) { + return [ + 'id' => $page->id, + 'title' => $page->title, + ]; + }, $this->repo->all()); + + $html = $this->renderer->render('page/list', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } +} diff --git a/implementation/18-caching/src/Bootstrap.php b/implementation/18-caching/src/Bootstrap.php new file mode 100644 index 0000000..3abc2e5 --- /dev/null +++ b/implementation/18-caching/src/Bootstrap.php @@ -0,0 +1,40 @@ +getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); diff --git a/implementation/18-caching/src/Exception/InternalServerError.php b/implementation/18-caching/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/18-caching/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +factory::fromGlobals(); + } + + /** + * @param UriInterface|string $uri + * @param array $serverParams + */ + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} diff --git a/implementation/18-caching/src/Factory/FileSystemSettingsProvider.php b/implementation/18-caching/src/Factory/FileSystemSettingsProvider.php new file mode 100644 index 0000000..f071078 --- /dev/null +++ b/implementation/18-caching/src/Factory/FileSystemSettingsProvider.php @@ -0,0 +1,22 @@ +filePath; + assert($settings instanceof Settings); + return $settings; + } +} diff --git a/implementation/18-caching/src/Factory/PipelineProvider.php b/implementation/18-caching/src/Factory/PipelineProvider.php new file mode 100644 index 0000000..77738f8 --- /dev/null +++ b/implementation/18-caching/src/Factory/PipelineProvider.php @@ -0,0 +1,25 @@ +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} diff --git a/implementation/18-caching/src/Factory/RequestFactory.php b/implementation/18-caching/src/Factory/RequestFactory.php new file mode 100644 index 0000000..2b17abc --- /dev/null +++ b/implementation/18-caching/src/Factory/RequestFactory.php @@ -0,0 +1,11 @@ +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = $settings; + $builder->addDefinitions($dependencies); + // $builder->enableCompilation('/tmp'); + return $builder->build(); + } +} diff --git a/implementation/18-caching/src/Factory/SettingsProvider.php b/implementation/18-caching/src/Factory/SettingsProvider.php new file mode 100644 index 0000000..ce1c5f0 --- /dev/null +++ b/implementation/18-caching/src/Factory/SettingsProvider.php @@ -0,0 +1,10 @@ +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + + echo $response->getBody(); + } +} diff --git a/implementation/18-caching/src/Http/ContainerPipeline.php b/implementation/18-caching/src/Http/ContainerPipeline.php new file mode 100644 index 0000000..816cedd --- /dev/null +++ b/implementation/18-caching/src/Http/ContainerPipeline.php @@ -0,0 +1,82 @@ + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} diff --git a/implementation/18-caching/src/Http/Emitter.php b/implementation/18-caching/src/Http/Emitter.php new file mode 100644 index 0000000..ce4c035 --- /dev/null +++ b/implementation/18-caching/src/Http/Emitter.php @@ -0,0 +1,10 @@ +getAttribute($this->routeAttributeName, false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} diff --git a/implementation/18-caching/src/Http/Pipeline.php b/implementation/18-caching/src/Http/Pipeline.php new file mode 100644 index 0000000..1a9dcda --- /dev/null +++ b/implementation/18-caching/src/Http/Pipeline.php @@ -0,0 +1,11 @@ +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} diff --git a/implementation/18-caching/src/Http/RoutedRequestHandler.php b/implementation/18-caching/src/Http/RoutedRequestHandler.php new file mode 100644 index 0000000..a7407c9 --- /dev/null +++ b/implementation/18-caching/src/Http/RoutedRequestHandler.php @@ -0,0 +1,10 @@ +pipeline->dispatch($request); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} diff --git a/implementation/18-caching/src/Middleware/Cache.php b/implementation/18-caching/src/Middleware/Cache.php new file mode 100644 index 0000000..8460761 --- /dev/null +++ b/implementation/18-caching/src/Middleware/Cache.php @@ -0,0 +1,38 @@ +getMethod() !== 'GET') { + return $handler->handle($request); + } + $keyHash = base64_encode($request->getUri()->getPath()); + $result = $this->cache->get( + $keyHash, + fn () => $this->serializer::toString($handler->handle($request)), + 300 + ); + assert(is_string($result)); + return $this->serializer::fromString($result); + } +} diff --git a/implementation/18-caching/src/Model/MarkdownPage.php b/implementation/18-caching/src/Model/MarkdownPage.php new file mode 100644 index 0000000..df244fd --- /dev/null +++ b/implementation/18-caching/src/Model/MarkdownPage.php @@ -0,0 +1,13 @@ +cache->get( + $key, + fn () => $this->repo->all(), + 300 + ); + assert(is_array($result)); + foreach ($result as $page) { + assert($page instanceof MarkdownPage); + } + return $result; + } + + public function byName(string $name): MarkdownPage + { + $key = base64_encode(self::class . 'byName' . $name); + $result = $this->cache->get( + $key, + fn () => $this->repo->byName($name), + 300 + ); + assert($result instanceof MarkdownPage); + return $result; + } +} diff --git a/implementation/18-caching/src/Repository/FileSystemMarkdownPageRepo.php b/implementation/18-caching/src/Repository/FileSystemMarkdownPageRepo.php new file mode 100644 index 0000000..cca350e --- /dev/null +++ b/implementation/18-caching/src/Repository/FileSystemMarkdownPageRepo.php @@ -0,0 +1,61 @@ +dataPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return array_map(function (string $filename) { + $content = file_get_contents($filename); + if ($content === false) { + throw new InternalServerError('cannot read pages'); + } + $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); + return new MarkdownPage( + (int) substr($idAndTitle, 0, 2), + substr($idAndTitle, 3), + $content + ); + }, $files); + } + + public function byName(string $name): MarkdownPage + { + $pages = array_values( + array_filter( + $this->all(), + fn (MarkdownPage $p) => $p->title === $name, + ) + ); + + if (count($pages) !== 1) { + throw new NotFound; + } + + return $pages[0]; + } +} diff --git a/implementation/18-caching/src/Repository/MarkdownPageRepo.php b/implementation/18-caching/src/Repository/MarkdownPageRepo.php new file mode 100644 index 0000000..0792d32 --- /dev/null +++ b/implementation/18-caching/src/Repository/MarkdownPageRepo.php @@ -0,0 +1,15 @@ +engine->render($template, $data); + } +} diff --git a/implementation/18-caching/src/Template/ParsedownParser.php b/implementation/18-caching/src/Template/ParsedownParser.php new file mode 100644 index 0000000..2ffd287 --- /dev/null +++ b/implementation/18-caching/src/Template/ParsedownParser.php @@ -0,0 +1,17 @@ +parser->parse($markdown); + } +} diff --git a/implementation/18-caching/src/Template/Renderer.php b/implementation/18-caching/src/Template/Renderer.php new file mode 100644 index 0000000..ff916ed --- /dev/null +++ b/implementation/18-caching/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data = []): string; +} diff --git a/implementation/18-caching/templates/hello.html b/implementation/18-caching/templates/hello.html new file mode 100644 index 0000000..15a4cd2 --- /dev/null +++ b/implementation/18-caching/templates/hello.html @@ -0,0 +1,6 @@ +{{> partials/head }} +
+

Hello {{name}}

+

The time is {{now}}

+
+{{> partials/foot }} diff --git a/implementation/18-caching/templates/page.html b/implementation/18-caching/templates/page.html new file mode 100644 index 0000000..c3c5284 --- /dev/null +++ b/implementation/18-caching/templates/page.html @@ -0,0 +1,5 @@ +{{> partials/head }} +
+ {{{content}}} +
+{{> partials/foot }} diff --git a/implementation/18-caching/templates/page/list.html b/implementation/18-caching/templates/page/list.html new file mode 100644 index 0000000..bf42348 --- /dev/null +++ b/implementation/18-caching/templates/page/list.html @@ -0,0 +1,19 @@ + + + + + Pages + + + +
+ +
+ + \ No newline at end of file diff --git a/implementation/18-caching/templates/page/show.html b/implementation/18-caching/templates/page/show.html new file mode 100644 index 0000000..abe295e --- /dev/null +++ b/implementation/18-caching/templates/page/show.html @@ -0,0 +1,17 @@ + + + + + {{title}} + + + + + + +
+ {{{content}}} +
+ + \ No newline at end of file diff --git a/implementation/18-caching/templates/pagelist.html b/implementation/18-caching/templates/pagelist.html new file mode 100644 index 0000000..538e2c4 --- /dev/null +++ b/implementation/18-caching/templates/pagelist.html @@ -0,0 +1,11 @@ +{{> partials/head }} +
+ +
+{{> partials/foot }} diff --git a/implementation/18-caching/templates/partials/foot.html b/implementation/18-caching/templates/partials/foot.html new file mode 100644 index 0000000..17c7245 --- /dev/null +++ b/implementation/18-caching/templates/partials/foot.html @@ -0,0 +1,3 @@ +
+ + \ No newline at end of file diff --git a/implementation/18-caching/templates/partials/head.html b/implementation/18-caching/templates/partials/head.html new file mode 100644 index 0000000..421d387 --- /dev/null +++ b/implementation/18-caching/templates/partials/head.html @@ -0,0 +1,11 @@ + + + + + No Framework: {{title}} + + + + + +
From 00c31259f8f738de70b10c6e955239be73492314 Mon Sep 17 00:00:00 2001 From: lubiana Date: Thu, 14 Apr 2022 07:48:16 +0200 Subject: [PATCH 124/314] prepare --- app/.php-cs-fixer.php | 38 - app/.phpcs.xml.dist | 9 - app/cli-config.php | 13 - app/composer.json | 57 - app/composer.lock | 2440 ---------- app/config/dependencies.php | 58 - app/config/middlewares.php | 13 - app/config/routes.php | 15 - app/config/settings.php | 12 - app/data/pages/01-front-controller.md | 53 - app/data/pages/02-composer.md | 75 - app/data/pages/03-error-handler.md | 79 - app/data/pages/04-development-helpers.md | 260 - app/data/pages/05-http.md | 124 - app/data/pages/06-router.md | 101 - app/data/pages/07-dispatching-to-a-class.md | 137 - app/data/pages/08-inversion-of-control.md | 54 - app/data/pages/09-dependency-injector.md | 213 - app/data/pages/10-invoker.md | 102 - app/data/pages/11-templating.md | 236 - app/data/pages/12-configuration.md | 200 - app/data/pages/13-refactoring.md | 373 -- app/data/pages/14-middleware.md | 303 -- app/data/pages/15-adding-content.md | 253 - app/data/pages/16-data-repository.md | 265 - app/data/pages/17-performance.md | 43 - app/data/pages/18-caching.md | 252 - app/phpstan-baseline.neon | 7 - app/phpstan.neon | 8 - app/public/css/spectre-exp.min.css | 1 - app/public/css/spectre-icons.min.css | 1 - app/public/css/spectre.min.css | 1 - app/public/index.php | 5 - app/src/.gitkeep | 0 app/src/Action/Hello.php | 31 - app/src/Action/Other.php | 16 - app/src/Action/Page.php | 60 - app/src/Bootstrap.php | 40 - app/src/Exception/InternalServerError.php | 9 - app/src/Exception/MethodNotAllowed.php | 9 - app/src/Exception/NotFound.php | 9 - app/src/Factory/ContainerProvider.php | 10 - app/src/Factory/DiactorosRequestFactory.php | 28 - .../Factory/FileSystemSettingsProvider.php | 22 - app/src/Factory/PipelineProvider.php | 25 - app/src/Factory/RequestFactory.php | 11 - app/src/Factory/SettingsContainerProvider.php | 26 - app/src/Factory/SettingsProvider.php | 10 - app/src/Http/BasicEmitter.php | 38 - app/src/Http/ContainerPipeline.php | 82 - app/src/Http/Emitter.php | 10 - app/src/Http/InvokerRoutedHandler.php | 34 - app/src/Http/Pipeline.php | 11 - app/src/Http/RouteMiddleware.php | 69 - app/src/Http/RoutedRequestHandler.php | 10 - app/src/Kernel.php | 32 - app/src/Middleware/Cache.php | 38 - app/src/Model/MarkdownPage.php | 13 - app/src/Repository/CachedMarkdownPageRepo.php | 49 - .../Repository/FileSystemMarkdownPageRepo.php | 61 - app/src/Repository/MarkdownPageRepo.php | 15 - app/src/Service/Cache/ApcuCache.php | 21 - app/src/Service/Cache/EasyCache.php | 9 - app/src/Service/Time/Now.php | 10 - app/src/Service/Time/SystemClockNow.php | 13 - app/src/Settings.php | 16 - app/src/Template/MarkdownParser.php | 8 - app/src/Template/MustacheRenderer.php | 17 - app/src/Template/ParsedownParser.php | 17 - app/src/Template/Renderer.php | 11 - app/templates/hello.html | 6 - app/templates/page.html | 5 - app/templates/page/list.html | 19 - app/templates/page/show.html | 17 - app/templates/pagelist.html | 11 - app/templates/partials/foot.html | 3 - app/templates/partials/head.html | 11 - .../01-front-controller/public/index.php | 5 - .../01-front-controller/src/Bootstrap.php | 3 - implementation/02-composer/composer.json | 17 - implementation/02-composer/composer.lock | 20 - implementation/02-composer/public/index.php | 5 - implementation/02-composer/src/Bootstrap.php | 3 - implementation/03-error-handler/composer.json | 22 - implementation/03-error-handler/composer.lock | 202 - .../03-error-handler/public/index.php | 5 - .../03-error-handler/src/Bootstrap.php | 25 - .../04-dev-helpers/.php-cs-fixer.php | 41 - implementation/04-dev-helpers/composer.json | 29 - implementation/04-dev-helpers/composer.lock | 420 -- implementation/04-dev-helpers/phpstan.neon | 4 - .../04-dev-helpers/public/index.php | 5 - .../04-dev-helpers/src/Bootstrap.php | 30 - implementation/05-http/.php-cs-fixer.php | 41 - implementation/05-http/composer.json | 30 - implementation/05-http/composer.lock | 627 --- implementation/05-http/phpstan-baseline.neon | 0 implementation/05-http/phpstan.neon | 4 - implementation/05-http/public/index.php | 5 - implementation/05-http/src/Bootstrap.php | 54 - implementation/06-router/.php-cs-fixer.php | 44 - implementation/06-router/composer.json | 31 - implementation/06-router/composer.lock | 677 --- implementation/06-router/config/routes.php | 17 - .../06-router/phpstan-baseline.neon | 0 implementation/06-router/phpstan.neon | 4 - implementation/06-router/public/index.php | 5 - implementation/06-router/src/Bootstrap.php | 89 - .../07-dispatching-to-class/.php-cs-fixer.php | 44 - .../07-dispatching-to-class/composer.json | 32 - .../07-dispatching-to-class/composer.lock | 734 --- .../07-dispatching-to-class/config/routes.php | 8 - .../phpstan-baseline.neon | 0 .../07-dispatching-to-class/phpstan.neon | 4 - .../07-dispatching-to-class/public/index.php | 5 - .../src/Action/Another.php | 20 - .../src/Action/Hello.php | 21 - .../src/Action/InternalServerError.php | 20 - .../src/Action/NotAllowed.php | 20 - .../src/Action/NotFound.php | 20 - .../07-dispatching-to-class/src/Bootstrap.php | 102 - .../InternalServerErrorException.php | 11 - .../src/Exception/NotAllowedException.php | 11 - .../src/Exception/NotFoundException.php | 11 - .../08-inversion-of-control/.php-cs-fixer.php | 44 - .../08-inversion-of-control/composer.json | 32 - .../08-inversion-of-control/composer.lock | 734 --- .../08-inversion-of-control/config/routes.php | 8 - .../phpstan-baseline.neon | 0 .../08-inversion-of-control/phpstan.neon | 4 - .../08-inversion-of-control/public/index.php | 5 - .../src/Action/Action.php | 17 - .../src/Action/Another.php | 19 - .../src/Action/Hello.php | 20 - .../src/Action/InternalServerError.php | 19 - .../src/Action/NotAllowed.php | 19 - .../src/Action/NotFound.php | 19 - .../08-inversion-of-control/src/Bootstrap.php | 102 - .../InternalServerErrorException.php | 11 - .../src/Exception/NotAllowedException.php | 11 - .../src/Exception/NotFoundException.php | 11 - .../09-dependency-injector/.php-cs-fixer.php | 44 - .../09-dependency-injector/composer.json | 34 - .../09-dependency-injector/composer.lock | 1020 ---- .../config/dependencies.php | 11 - .../09-dependency-injector/config/routes.php | 8 - .../phpstan-baseline.neon | 7 - .../09-dependency-injector/phpstan.neon | 7 - .../09-dependency-injector/public/index.php | 5 - .../src/Action/Action.php | 18 - .../src/Action/Another.php | 19 - .../src/Action/Hello.php | 20 - .../src/Action/InternalServerError.php | 19 - .../src/Action/NotAllowed.php | 19 - .../src/Action/NotFound.php | 19 - .../09-dependency-injector/src/Bootstrap.php | 106 - .../InternalServerErrorException.php | 11 - .../src/Exception/NotAllowedException.php | 11 - .../src/Exception/NotFoundException.php | 11 - implementation/10-invoker/.php-cs-fixer.php | 47 - implementation/10-invoker/composer.json | 32 - implementation/10-invoker/composer.lock | 1020 ---- .../10-invoker/config/dependencies.php | 12 - implementation/10-invoker/config/routes.php | 18 - implementation/10-invoker/phpstan.neon | 5 - implementation/10-invoker/public/favicon.ico | Bin 15086 -> 0 bytes implementation/10-invoker/public/index.php | 6 - implementation/10-invoker/src/.gitkeep | 0 .../10-invoker/src/Action/Hello.php | 23 - .../10-invoker/src/Action/Other.php | 20 - implementation/10-invoker/src/Bootstrap.php | 110 - .../src/Exception/InternalServerError.php | 11 - .../src/Exception/MethodNotAllowed.php | 11 - .../10-invoker/src/Exception/NotFound.php | 11 - .../10-invoker/src/Service/Time/Now.php | 12 - .../src/Service/Time/SystemClockNow.php | 15 - .../11-templating/.php-cs-fixer.php | 38 - implementation/11-templating/.phpcs.xml.dist | 9 - implementation/11-templating/composer.json | 46 - implementation/11-templating/composer.lock | 1550 ------ .../11-templating/config/dependencies.php | 32 - .../11-templating/config/routes.php | 12 - .../11-templating/config/settings.php | 9 - .../11-templating/phpstan-baseline.neon | 7 - implementation/11-templating/phpstan.neon | 8 - .../11-templating/public/favicon.ico | Bin 15086 -> 0 bytes implementation/11-templating/public/index.php | 5 - implementation/11-templating/src/.gitkeep | 0 .../11-templating/src/Action/Hello.php | 31 - .../11-templating/src/Action/Other.php | 19 - .../11-templating/src/Bootstrap.php | 110 - .../src/Exception/InternalServerError.php | 9 - .../src/Exception/MethodNotAllowed.php | 9 - .../11-templating/src/Exception/NotFound.php | 9 - .../11-templating/src/Service/Time/Now.php | 10 - .../src/Service/Time/SystemClockNow.php | 13 - implementation/11-templating/src/Settings.php | 13 - .../src/Template/MustacheRenderer.php | 17 - .../11-templating/src/Template/Renderer.php | 11 - .../11-templating/templates/hello.html | 11 - .../12-configuration/.php-cs-fixer.php | 38 - .../12-configuration/.phpcs.xml.dist | 9 - implementation/12-configuration/composer.json | 46 - implementation/12-configuration/composer.lock | 1550 ------ .../12-configuration/config/dependencies.php | 22 - .../12-configuration/config/routes.php | 12 - .../12-configuration/config/settings.php | 10 - .../12-configuration/phpstan-baseline.neon | 0 implementation/12-configuration/phpstan.neon | 8 - .../12-configuration/public/favicon.ico | Bin 15086 -> 0 bytes .../12-configuration/public/index.php | 5 - implementation/12-configuration/src/.gitkeep | 0 .../12-configuration/src/Action/Hello.php | 31 - .../12-configuration/src/Action/Other.php | 19 - .../12-configuration/src/Bootstrap.php | 111 - .../src/Exception/InternalServerError.php | 9 - .../src/Exception/MethodNotAllowed.php | 9 - .../src/Exception/NotFound.php | 9 - .../src/Factory/ContainerProvider.php | 10 - .../Factory/FileSystemSettingsProvider.php | 18 - .../src/Factory/SettingsContainerProvider.php | 25 - .../src/Factory/SettingsProvider.php | 10 - .../12-configuration/src/Service/Time/Now.php | 10 - .../src/Service/Time/SystemClockNow.php | 13 - .../12-configuration/src/Settings.php | 14 - .../src/Template/MustacheRenderer.php | 17 - .../src/Template/Renderer.php | 11 - .../12-configuration/templates/hello.html | 11 - .../13-refactoring/.php-cs-fixer.php | 38 - implementation/13-refactoring/.phpcs.xml.dist | 9 - implementation/13-refactoring/composer.json | 47 - implementation/13-refactoring/composer.lock | 1607 ------- .../13-refactoring/config/dependencies.php | 39 - .../13-refactoring/config/routes.php | 12 - .../13-refactoring/config/settings.php | 10 - .../13-refactoring/phpstan-baseline.neon | 7 - implementation/13-refactoring/phpstan.neon | 8 - .../13-refactoring/public/favicon.ico | Bin 15086 -> 0 bytes .../13-refactoring/public/index.php | 5 - implementation/13-refactoring/src/.gitkeep | 0 .../13-refactoring/src/Action/Hello.php | 31 - .../13-refactoring/src/Action/Other.php | 19 - .../13-refactoring/src/Bootstrap.php | 40 - .../src/Exception/InternalServerError.php | 9 - .../src/Exception/MethodNotAllowed.php | 9 - .../13-refactoring/src/Exception/NotFound.php | 9 - .../src/Factory/ContainerProvider.php | 10 - .../src/Factory/DiactorosRequestFactory.php | 23 - .../Factory/FileSystemSettingsProvider.php | 18 - .../src/Factory/RequestFactory.php | 11 - .../src/Factory/SettingsContainerProvider.php | 25 - .../src/Factory/SettingsProvider.php | 10 - .../13-refactoring/src/Http/BasicEmitter.php | 38 - .../13-refactoring/src/Http/Emitter.php | 10 - .../src/Http/InvokerRoutedHandler.php | 37 - .../src/Http/RouteMiddleware.php | 69 - .../src/Http/RoutedRequestHandler.php | 10 - implementation/13-refactoring/src/Kernel.php | 34 - .../13-refactoring/src/Service/Time/Now.php | 10 - .../src/Service/Time/SystemClockNow.php | 13 - .../13-refactoring/src/Settings.php | 14 - .../src/Template/MustacheRenderer.php | 17 - .../13-refactoring/src/Template/Renderer.php | 11 - .../13-refactoring/templates/hello.html | 11 - .../14-middleware/.php-cs-fixer.php | 38 - implementation/14-middleware/.phpcs.xml.dist | 9 - implementation/14-middleware/composer.json | 50 - implementation/14-middleware/composer.lock | 1815 ------- .../14-middleware/config/dependencies.php | 42 - .../14-middleware/config/middlewares.php | 11 - .../14-middleware/config/routes.php | 12 - .../14-middleware/config/settings.php | 11 - .../14-middleware/phpstan-baseline.neon | 7 - implementation/14-middleware/phpstan.neon | 8 - .../14-middleware/public/favicon.ico | Bin 15086 -> 0 bytes implementation/14-middleware/public/index.php | 5 - implementation/14-middleware/src/.gitkeep | 0 .../14-middleware/src/Action/Hello.php | 31 - .../14-middleware/src/Action/Other.php | 19 - .../14-middleware/src/Bootstrap.php | 40 - .../src/Exception/InternalServerError.php | 9 - .../src/Exception/MethodNotAllowed.php | 9 - .../14-middleware/src/Exception/NotFound.php | 9 - .../src/Factory/ContainerProvider.php | 10 - .../src/Factory/DiactorosRequestFactory.php | 28 - .../Factory/FileSystemSettingsProvider.php | 22 - .../src/Factory/PipelineProvider.php | 25 - .../src/Factory/RequestFactory.php | 11 - .../src/Factory/SettingsContainerProvider.php | 25 - .../src/Factory/SettingsProvider.php | 10 - .../14-middleware/src/Http/BasicEmitter.php | 38 - .../src/Http/ContainerPipeline.php | 82 - .../14-middleware/src/Http/Emitter.php | 10 - .../src/Http/InvokerRoutedHandler.php | 34 - .../14-middleware/src/Http/Pipeline.php | 11 - .../src/Http/RouteMiddleware.php | 69 - .../src/Http/RoutedRequestHandler.php | 10 - implementation/14-middleware/src/Kernel.php | 32 - .../14-middleware/src/Service/Time/Now.php | 10 - .../src/Service/Time/SystemClockNow.php | 13 - implementation/14-middleware/src/Settings.php | 15 - .../src/Template/MustacheRenderer.php | 17 - .../14-middleware/src/Template/Renderer.php | 11 - .../14-middleware/templates/hello.html | 11 - .../15-adding-content/.php-cs-fixer.php | 38 - .../15-adding-content/.phpcs.xml.dist | 9 - .../15-adding-content/cli-config.php | 13 - .../15-adding-content/composer.json | 56 - .../15-adding-content/composer.lock | 4246 ----------------- .../15-adding-content/config/dependencies.php | 60 - .../15-adding-content/config/middlewares.php | 11 - .../15-adding-content/config/routes.php | 15 - .../15-adding-content/config/settings.php | 23 - .../data/pages/01-front-controller.md | 53 - .../data/pages/02-composer.md | 75 - .../data/pages/03-error-handler.md | 79 - .../data/pages/04-development-helpers.md | 260 - .../15-adding-content/data/pages/05-http.md | 124 - .../15-adding-content/data/pages/06-router.md | 101 - .../data/pages/07-dispatching-to-a-class.md | 137 - .../data/pages/08-inversion-of-control.md | 54 - .../data/pages/09-dependency-injector.md | 213 - .../data/pages/10-invoker.md | 102 - .../data/pages/11-templating.md | 240 - .../data/pages/12-configuration.md | 201 - .../data/pages/13-refactoring.md | 377 -- .../data/pages/14-middleware.md | 298 -- .../15-adding-content/phpstan-baseline.neon | 7 - implementation/15-adding-content/phpstan.neon | 8 - .../public/css/spectre-exp.min.css | 1 - .../public/css/spectre-icons.min.css | 1 - .../public/css/spectre.min.css | 1 - .../15-adding-content/public/favicon.ico | Bin 15086 -> 0 bytes .../15-adding-content/public/index.php | 5 - implementation/15-adding-content/src/.gitkeep | 0 .../15-adding-content/src/Action/Hello.php | 31 - .../15-adding-content/src/Action/Other.php | 16 - .../15-adding-content/src/Action/Page.php | 80 - .../15-adding-content/src/Bootstrap.php | 40 - .../src/Exception/InternalServerError.php | 9 - .../src/Exception/MethodNotAllowed.php | 9 - .../src/Exception/NotFound.php | 9 - .../src/Factory/ContainerProvider.php | 10 - .../src/Factory/DiactorosRequestFactory.php | 28 - .../src/Factory/DoctrineEm.php | 32 - .../Factory/FileSystemSettingsProvider.php | 22 - .../src/Factory/PipelineProvider.php | 25 - .../src/Factory/RequestFactory.php | 11 - .../src/Factory/SettingsContainerProvider.php | 26 - .../src/Factory/SettingsProvider.php | 10 - .../src/Http/BasicEmitter.php | 38 - .../src/Http/ContainerPipeline.php | 82 - .../15-adding-content/src/Http/Emitter.php | 10 - .../src/Http/InvokerRoutedHandler.php | 34 - .../15-adding-content/src/Http/Pipeline.php | 11 - .../src/Http/RouteMiddleware.php | 69 - .../src/Http/RoutedRequestHandler.php | 10 - .../15-adding-content/src/Kernel.php | 32 - .../src/Middleware/CacheMiddleware.php | 40 - .../src/Model/MarkdownPage.php | 21 - .../src/Repository/CachedMarkdownPageRepo.php | 62 - .../Repository/DoctrineMarkdownPageRepo.php | 59 - .../src/Repository/MarkdownPageFilesystem.php | 69 - .../src/Repository/MarkdownPageRepo.php | 19 - .../src/Service/Time/Now.php | 10 - .../src/Service/Time/SystemClockNow.php | 13 - .../15-adding-content/src/Settings.php | 33 - .../src/Template/GithubMarkdownRenderer.php | 28 - .../src/Template/MarkdownParser.php | 8 - .../src/Template/MustacheRenderer.php | 17 - .../src/Template/ParsedownParser.php | 17 - .../src/Template/Renderer.php | 11 - .../15-adding-content/templates/hello.html | 6 - .../15-adding-content/templates/page.html | 5 - .../templates/page/list.html | 19 - .../templates/page/show.html | 17 - .../15-adding-content/templates/pagelist.html | 11 - .../templates/partials/foot.html | 3 - .../templates/partials/head.html | 11 - implementation/16-caching/.php-cs-fixer.php | 38 - implementation/16-caching/.phpcs.xml.dist | 9 - implementation/16-caching/composer.json | 54 - implementation/16-caching/composer.lock | 2273 --------- .../16-caching/config/dependencies.php | 54 - .../16-caching/config/middlewares.php | 13 - implementation/16-caching/config/routes.php | 14 - implementation/16-caching/config/settings.php | 12 - .../data/pages/01-front-controller.md | 53 - .../16-caching/data/pages/02-composer.md | 75 - .../16-caching/data/pages/03-error-handler.md | 79 - .../data/pages/04-development-helpers.md | 260 - .../16-caching/data/pages/05-http.md | 124 - .../16-caching/data/pages/06-router.md | 101 - .../data/pages/07-dispatching-to-a-class.md | 137 - .../data/pages/08-inversion-of-control.md | 54 - .../data/pages/09-dependency-injector.md | 213 - .../16-caching/data/pages/10-invoker.md | 102 - .../16-caching/data/pages/11-templating.md | 240 - .../16-caching/data/pages/12-configuration.md | 201 - .../16-caching/data/pages/13-refactoring.md | 377 -- .../16-caching/data/pages/14-middleware.md | 298 -- .../16-caching/phpstan-baseline.neon | 7 - implementation/16-caching/phpstan.neon | 8 - .../16-caching/public/css/spectre-exp.min.css | 1 - .../public/css/spectre-icons.min.css | 1 - .../16-caching/public/css/spectre.min.css | 1 - implementation/16-caching/public/favicon.ico | Bin 15086 -> 0 bytes implementation/16-caching/public/index.php | 5 - implementation/16-caching/src/.gitkeep | 0 .../16-caching/src/Action/Hello.php | 31 - .../16-caching/src/Action/Other.php | 19 - implementation/16-caching/src/Action/Page.php | 35 - implementation/16-caching/src/Bootstrap.php | 40 - .../src/Exception/InternalServerError.php | 9 - .../src/Exception/MethodNotAllowed.php | 9 - .../16-caching/src/Exception/NotFound.php | 9 - .../src/Factory/ContainerProvider.php | 10 - .../src/Factory/DiactorosRequestFactory.php | 28 - .../Factory/FileSystemSettingsProvider.php | 22 - .../src/Factory/PipelineProvider.php | 25 - .../16-caching/src/Factory/RequestFactory.php | 11 - .../src/Factory/SettingsContainerProvider.php | 25 - .../src/Factory/SettingsProvider.php | 10 - .../16-caching/src/Http/BasicEmitter.php | 38 - .../16-caching/src/Http/ContainerPipeline.php | 82 - .../16-caching/src/Http/Emitter.php | 10 - .../src/Http/InvokerRoutedHandler.php | 34 - .../16-caching/src/Http/Pipeline.php | 11 - .../16-caching/src/Http/RouteMiddleware.php | 69 - .../src/Http/RoutedRequestHandler.php | 10 - implementation/16-caching/src/Kernel.php | 32 - .../src/Middleware/CacheMiddleware.php | 36 - .../16-caching/src/Model/MarkdownPage.php | 13 - .../src/Repository/CachedMarkdownPageRepo.php | 46 - .../src/Repository/MarkdownPageFilesystem.php | 63 - .../src/Repository/MarkdownPageRepo.php | 17 - .../16-caching/src/Service/Time/Now.php | 10 - .../src/Service/Time/SystemClockNow.php | 13 - implementation/16-caching/src/Settings.php | 16 - .../src/Template/MustacheRenderer.php | 17 - .../16-caching/src/Template/Renderer.php | 11 - .../16-caching/templates/hello.html | 6 - implementation/16-caching/templates/page.html | 5 - .../16-caching/templates/partials/foot.html | 3 - .../16-caching/templates/partials/head.html | 11 - .../16-data-repository/.php-cs-fixer.php | 38 - .../16-data-repository/.phpcs.xml.dist | 9 - .../16-data-repository/cli-config.php | 13 - .../16-data-repository/composer.json | 54 - .../16-data-repository/composer.lock | 2438 ---------- .../config/dependencies.php | 55 - .../16-data-repository/config/middlewares.php | 11 - .../16-data-repository/config/routes.php | 15 - .../16-data-repository/config/settings.php | 12 - .../data/pages/01-front-controller.md | 53 - .../data/pages/02-composer.md | 75 - .../data/pages/03-error-handler.md | 79 - .../data/pages/04-development-helpers.md | 260 - .../16-data-repository/data/pages/05-http.md | 124 - .../data/pages/06-router.md | 101 - .../data/pages/07-dispatching-to-a-class.md | 137 - .../data/pages/08-inversion-of-control.md | 54 - .../data/pages/09-dependency-injector.md | 213 - .../data/pages/10-invoker.md | 102 - .../data/pages/11-templating.md | 236 - .../data/pages/12-configuration.md | 201 - .../data/pages/13-refactoring.md | 377 -- .../data/pages/14-middleware.md | 298 -- .../16-data-repository/phpstan-baseline.neon | 7 - .../16-data-repository/phpstan.neon | 8 - .../public/css/spectre-exp.min.css | 1 - .../public/css/spectre-icons.min.css | 1 - .../public/css/spectre.min.css | 1 - .../16-data-repository/public/favicon.ico | Bin 15086 -> 0 bytes .../16-data-repository/public/index.php | 5 - .../16-data-repository/src/.gitkeep | 0 .../16-data-repository/src/Action/Hello.php | 31 - .../16-data-repository/src/Action/Other.php | 16 - .../16-data-repository/src/Action/Page.php | 60 - .../16-data-repository/src/Bootstrap.php | 40 - .../src/Exception/InternalServerError.php | 9 - .../src/Exception/MethodNotAllowed.php | 9 - .../src/Exception/NotFound.php | 9 - .../src/Factory/ContainerProvider.php | 10 - .../src/Factory/DiactorosRequestFactory.php | 28 - .../src/Factory/DoctrineEm.php | 32 - .../Factory/FileSystemSettingsProvider.php | 22 - .../src/Factory/PipelineProvider.php | 25 - .../src/Factory/RequestFactory.php | 11 - .../src/Factory/SettingsContainerProvider.php | 26 - .../src/Factory/SettingsProvider.php | 10 - .../src/Http/BasicEmitter.php | 38 - .../src/Http/ContainerPipeline.php | 82 - .../16-data-repository/src/Http/Emitter.php | 10 - .../src/Http/InvokerRoutedHandler.php | 34 - .../16-data-repository/src/Http/Pipeline.php | 11 - .../src/Http/RouteMiddleware.php | 69 - .../src/Http/RoutedRequestHandler.php | 10 - .../16-data-repository/src/Kernel.php | 32 - .../src/Model/MarkdownPage.php | 13 - .../Repository/FileSystemMarkdownPageRepo.php | 61 - .../src/Repository/MarkdownPageRepo.php | 15 - .../src/Service/Time/Now.php | 10 - .../src/Service/Time/SystemClockNow.php | 13 - .../16-data-repository/src/Settings.php | 16 - .../src/Template/MarkdownParser.php | 8 - .../src/Template/MustacheRenderer.php | 17 - .../src/Template/ParsedownParser.php | 17 - .../src/Template/Renderer.php | 11 - .../16-data-repository/templates/hello.html | 6 - .../16-data-repository/templates/page.html | 5 - .../templates/page/list.html | 19 - .../templates/page/show.html | 17 - .../templates/pagelist.html | 11 - .../templates/partials/foot.html | 3 - .../templates/partials/head.html | 11 - implementation/18-caching/.php-cs-fixer.php | 38 - implementation/18-caching/.phpcs.xml.dist | 9 - implementation/18-caching/cli-config.php | 13 - implementation/18-caching/composer.json | 57 - implementation/18-caching/composer.lock | 2440 ---------- .../18-caching/config/dependencies.php | 58 - .../18-caching/config/middlewares.php | 13 - implementation/18-caching/config/routes.php | 15 - implementation/18-caching/config/settings.php | 12 - .../data/pages/01-front-controller.md | 53 - .../18-caching/data/pages/02-composer.md | 75 - .../18-caching/data/pages/03-error-handler.md | 79 - .../data/pages/04-development-helpers.md | 260 - .../18-caching/data/pages/05-http.md | 124 - .../18-caching/data/pages/06-router.md | 101 - .../data/pages/07-dispatching-to-a-class.md | 137 - .../data/pages/08-inversion-of-control.md | 54 - .../data/pages/09-dependency-injector.md | 213 - .../18-caching/data/pages/10-invoker.md | 102 - .../18-caching/data/pages/11-templating.md | 236 - .../18-caching/data/pages/12-configuration.md | 200 - .../18-caching/data/pages/13-refactoring.md | 373 -- .../18-caching/data/pages/14-middleware.md | 303 -- .../data/pages/15-adding-content.md | 253 - .../data/pages/16-data-repository.md | 265 - .../18-caching/data/pages/17-performance.md | 43 - .../18-caching/data/pages/18-caching.md | 252 - .../18-caching/phpstan-baseline.neon | 7 - implementation/18-caching/phpstan.neon | 8 - .../18-caching/public/css/spectre-exp.min.css | 1 - .../public/css/spectre-icons.min.css | 1 - .../18-caching/public/css/spectre.min.css | 1 - implementation/18-caching/public/favicon.ico | Bin 15086 -> 0 bytes implementation/18-caching/public/index.php | 5 - implementation/18-caching/src/.gitkeep | 0 .../18-caching/src/Action/Hello.php | 31 - .../18-caching/src/Action/Other.php | 16 - implementation/18-caching/src/Action/Page.php | 60 - implementation/18-caching/src/Bootstrap.php | 40 - .../src/Exception/InternalServerError.php | 9 - .../src/Exception/MethodNotAllowed.php | 9 - .../18-caching/src/Exception/NotFound.php | 9 - .../src/Factory/ContainerProvider.php | 10 - .../src/Factory/DiactorosRequestFactory.php | 28 - .../Factory/FileSystemSettingsProvider.php | 22 - .../src/Factory/PipelineProvider.php | 25 - .../18-caching/src/Factory/RequestFactory.php | 11 - .../src/Factory/SettingsContainerProvider.php | 26 - .../src/Factory/SettingsProvider.php | 10 - .../18-caching/src/Http/BasicEmitter.php | 38 - .../18-caching/src/Http/ContainerPipeline.php | 82 - .../18-caching/src/Http/Emitter.php | 10 - .../src/Http/InvokerRoutedHandler.php | 34 - .../18-caching/src/Http/Pipeline.php | 11 - .../18-caching/src/Http/RouteMiddleware.php | 69 - .../src/Http/RoutedRequestHandler.php | 10 - implementation/18-caching/src/Kernel.php | 32 - .../18-caching/src/Middleware/Cache.php | 38 - .../18-caching/src/Model/MarkdownPage.php | 13 - .../src/Repository/CachedMarkdownPageRepo.php | 49 - .../Repository/FileSystemMarkdownPageRepo.php | 61 - .../src/Repository/MarkdownPageRepo.php | 15 - .../src/Service/Cache/ApcuCache.php | 21 - .../src/Service/Cache/EasyCache.php | 9 - .../18-caching/src/Service/Time/Now.php | 10 - .../src/Service/Time/SystemClockNow.php | 13 - implementation/18-caching/src/Settings.php | 16 - .../src/Template/MarkdownParser.php | 8 - .../src/Template/MustacheRenderer.php | 17 - .../src/Template/ParsedownParser.php | 17 - .../18-caching/src/Template/Renderer.php | 11 - .../18-caching/templates/hello.html | 6 - implementation/18-caching/templates/page.html | 5 - .../18-caching/templates/page/list.html | 19 - .../18-caching/templates/page/show.html | 17 - .../18-caching/templates/pagelist.html | 11 - .../18-caching/templates/partials/foot.html | 3 - .../18-caching/templates/partials/head.html | 11 - to-be-continued.md | 17 - 595 files changed, 49207 deletions(-) delete mode 100644 app/.php-cs-fixer.php delete mode 100644 app/.phpcs.xml.dist delete mode 100644 app/cli-config.php delete mode 100644 app/composer.json delete mode 100644 app/composer.lock delete mode 100644 app/config/dependencies.php delete mode 100644 app/config/middlewares.php delete mode 100644 app/config/routes.php delete mode 100644 app/config/settings.php delete mode 100644 app/data/pages/01-front-controller.md delete mode 100644 app/data/pages/02-composer.md delete mode 100644 app/data/pages/03-error-handler.md delete mode 100644 app/data/pages/04-development-helpers.md delete mode 100644 app/data/pages/05-http.md delete mode 100644 app/data/pages/06-router.md delete mode 100644 app/data/pages/07-dispatching-to-a-class.md delete mode 100644 app/data/pages/08-inversion-of-control.md delete mode 100644 app/data/pages/09-dependency-injector.md delete mode 100644 app/data/pages/10-invoker.md delete mode 100644 app/data/pages/11-templating.md delete mode 100644 app/data/pages/12-configuration.md delete mode 100644 app/data/pages/13-refactoring.md delete mode 100644 app/data/pages/14-middleware.md delete mode 100644 app/data/pages/15-adding-content.md delete mode 100644 app/data/pages/16-data-repository.md delete mode 100644 app/data/pages/17-performance.md delete mode 100644 app/data/pages/18-caching.md delete mode 100644 app/phpstan-baseline.neon delete mode 100644 app/phpstan.neon delete mode 100644 app/public/css/spectre-exp.min.css delete mode 100644 app/public/css/spectre-icons.min.css delete mode 100644 app/public/css/spectre.min.css delete mode 100644 app/public/index.php delete mode 100644 app/src/.gitkeep delete mode 100644 app/src/Action/Hello.php delete mode 100644 app/src/Action/Other.php delete mode 100644 app/src/Action/Page.php delete mode 100644 app/src/Bootstrap.php delete mode 100644 app/src/Exception/InternalServerError.php delete mode 100644 app/src/Exception/MethodNotAllowed.php delete mode 100644 app/src/Exception/NotFound.php delete mode 100644 app/src/Factory/ContainerProvider.php delete mode 100644 app/src/Factory/DiactorosRequestFactory.php delete mode 100644 app/src/Factory/FileSystemSettingsProvider.php delete mode 100644 app/src/Factory/PipelineProvider.php delete mode 100644 app/src/Factory/RequestFactory.php delete mode 100644 app/src/Factory/SettingsContainerProvider.php delete mode 100644 app/src/Factory/SettingsProvider.php delete mode 100644 app/src/Http/BasicEmitter.php delete mode 100644 app/src/Http/ContainerPipeline.php delete mode 100644 app/src/Http/Emitter.php delete mode 100644 app/src/Http/InvokerRoutedHandler.php delete mode 100644 app/src/Http/Pipeline.php delete mode 100644 app/src/Http/RouteMiddleware.php delete mode 100644 app/src/Http/RoutedRequestHandler.php delete mode 100644 app/src/Kernel.php delete mode 100644 app/src/Middleware/Cache.php delete mode 100644 app/src/Model/MarkdownPage.php delete mode 100644 app/src/Repository/CachedMarkdownPageRepo.php delete mode 100644 app/src/Repository/FileSystemMarkdownPageRepo.php delete mode 100644 app/src/Repository/MarkdownPageRepo.php delete mode 100644 app/src/Service/Cache/ApcuCache.php delete mode 100644 app/src/Service/Cache/EasyCache.php delete mode 100644 app/src/Service/Time/Now.php delete mode 100644 app/src/Service/Time/SystemClockNow.php delete mode 100644 app/src/Settings.php delete mode 100644 app/src/Template/MarkdownParser.php delete mode 100644 app/src/Template/MustacheRenderer.php delete mode 100644 app/src/Template/ParsedownParser.php delete mode 100644 app/src/Template/Renderer.php delete mode 100644 app/templates/hello.html delete mode 100644 app/templates/page.html delete mode 100644 app/templates/page/list.html delete mode 100644 app/templates/page/show.html delete mode 100644 app/templates/pagelist.html delete mode 100644 app/templates/partials/foot.html delete mode 100644 app/templates/partials/head.html delete mode 100644 implementation/01-front-controller/public/index.php delete mode 100644 implementation/01-front-controller/src/Bootstrap.php delete mode 100644 implementation/02-composer/composer.json delete mode 100644 implementation/02-composer/composer.lock delete mode 100644 implementation/02-composer/public/index.php delete mode 100644 implementation/02-composer/src/Bootstrap.php delete mode 100644 implementation/03-error-handler/composer.json delete mode 100644 implementation/03-error-handler/composer.lock delete mode 100644 implementation/03-error-handler/public/index.php delete mode 100644 implementation/03-error-handler/src/Bootstrap.php delete mode 100644 implementation/04-dev-helpers/.php-cs-fixer.php delete mode 100644 implementation/04-dev-helpers/composer.json delete mode 100644 implementation/04-dev-helpers/composer.lock delete mode 100644 implementation/04-dev-helpers/phpstan.neon delete mode 100644 implementation/04-dev-helpers/public/index.php delete mode 100644 implementation/04-dev-helpers/src/Bootstrap.php delete mode 100644 implementation/05-http/.php-cs-fixer.php delete mode 100644 implementation/05-http/composer.json delete mode 100644 implementation/05-http/composer.lock delete mode 100644 implementation/05-http/phpstan-baseline.neon delete mode 100644 implementation/05-http/phpstan.neon delete mode 100644 implementation/05-http/public/index.php delete mode 100644 implementation/05-http/src/Bootstrap.php delete mode 100644 implementation/06-router/.php-cs-fixer.php delete mode 100644 implementation/06-router/composer.json delete mode 100644 implementation/06-router/composer.lock delete mode 100644 implementation/06-router/config/routes.php delete mode 100644 implementation/06-router/phpstan-baseline.neon delete mode 100644 implementation/06-router/phpstan.neon delete mode 100644 implementation/06-router/public/index.php delete mode 100644 implementation/06-router/src/Bootstrap.php delete mode 100644 implementation/07-dispatching-to-class/.php-cs-fixer.php delete mode 100644 implementation/07-dispatching-to-class/composer.json delete mode 100644 implementation/07-dispatching-to-class/composer.lock delete mode 100644 implementation/07-dispatching-to-class/config/routes.php delete mode 100644 implementation/07-dispatching-to-class/phpstan-baseline.neon delete mode 100644 implementation/07-dispatching-to-class/phpstan.neon delete mode 100644 implementation/07-dispatching-to-class/public/index.php delete mode 100644 implementation/07-dispatching-to-class/src/Action/Another.php delete mode 100644 implementation/07-dispatching-to-class/src/Action/Hello.php delete mode 100644 implementation/07-dispatching-to-class/src/Action/InternalServerError.php delete mode 100644 implementation/07-dispatching-to-class/src/Action/NotAllowed.php delete mode 100644 implementation/07-dispatching-to-class/src/Action/NotFound.php delete mode 100644 implementation/07-dispatching-to-class/src/Bootstrap.php delete mode 100644 implementation/07-dispatching-to-class/src/Exception/InternalServerErrorException.php delete mode 100644 implementation/07-dispatching-to-class/src/Exception/NotAllowedException.php delete mode 100644 implementation/07-dispatching-to-class/src/Exception/NotFoundException.php delete mode 100644 implementation/08-inversion-of-control/.php-cs-fixer.php delete mode 100644 implementation/08-inversion-of-control/composer.json delete mode 100644 implementation/08-inversion-of-control/composer.lock delete mode 100644 implementation/08-inversion-of-control/config/routes.php delete mode 100644 implementation/08-inversion-of-control/phpstan-baseline.neon delete mode 100644 implementation/08-inversion-of-control/phpstan.neon delete mode 100644 implementation/08-inversion-of-control/public/index.php delete mode 100644 implementation/08-inversion-of-control/src/Action/Action.php delete mode 100644 implementation/08-inversion-of-control/src/Action/Another.php delete mode 100644 implementation/08-inversion-of-control/src/Action/Hello.php delete mode 100644 implementation/08-inversion-of-control/src/Action/InternalServerError.php delete mode 100644 implementation/08-inversion-of-control/src/Action/NotAllowed.php delete mode 100644 implementation/08-inversion-of-control/src/Action/NotFound.php delete mode 100644 implementation/08-inversion-of-control/src/Bootstrap.php delete mode 100644 implementation/08-inversion-of-control/src/Exception/InternalServerErrorException.php delete mode 100644 implementation/08-inversion-of-control/src/Exception/NotAllowedException.php delete mode 100644 implementation/08-inversion-of-control/src/Exception/NotFoundException.php delete mode 100644 implementation/09-dependency-injector/.php-cs-fixer.php delete mode 100644 implementation/09-dependency-injector/composer.json delete mode 100644 implementation/09-dependency-injector/composer.lock delete mode 100644 implementation/09-dependency-injector/config/dependencies.php delete mode 100644 implementation/09-dependency-injector/config/routes.php delete mode 100644 implementation/09-dependency-injector/phpstan-baseline.neon delete mode 100644 implementation/09-dependency-injector/phpstan.neon delete mode 100644 implementation/09-dependency-injector/public/index.php delete mode 100644 implementation/09-dependency-injector/src/Action/Action.php delete mode 100644 implementation/09-dependency-injector/src/Action/Another.php delete mode 100644 implementation/09-dependency-injector/src/Action/Hello.php delete mode 100644 implementation/09-dependency-injector/src/Action/InternalServerError.php delete mode 100644 implementation/09-dependency-injector/src/Action/NotAllowed.php delete mode 100644 implementation/09-dependency-injector/src/Action/NotFound.php delete mode 100644 implementation/09-dependency-injector/src/Bootstrap.php delete mode 100644 implementation/09-dependency-injector/src/Exception/InternalServerErrorException.php delete mode 100644 implementation/09-dependency-injector/src/Exception/NotAllowedException.php delete mode 100644 implementation/09-dependency-injector/src/Exception/NotFoundException.php delete mode 100644 implementation/10-invoker/.php-cs-fixer.php delete mode 100644 implementation/10-invoker/composer.json delete mode 100644 implementation/10-invoker/composer.lock delete mode 100644 implementation/10-invoker/config/dependencies.php delete mode 100644 implementation/10-invoker/config/routes.php delete mode 100644 implementation/10-invoker/phpstan.neon delete mode 100644 implementation/10-invoker/public/favicon.ico delete mode 100644 implementation/10-invoker/public/index.php delete mode 100644 implementation/10-invoker/src/.gitkeep delete mode 100644 implementation/10-invoker/src/Action/Hello.php delete mode 100644 implementation/10-invoker/src/Action/Other.php delete mode 100644 implementation/10-invoker/src/Bootstrap.php delete mode 100644 implementation/10-invoker/src/Exception/InternalServerError.php delete mode 100644 implementation/10-invoker/src/Exception/MethodNotAllowed.php delete mode 100644 implementation/10-invoker/src/Exception/NotFound.php delete mode 100644 implementation/10-invoker/src/Service/Time/Now.php delete mode 100644 implementation/10-invoker/src/Service/Time/SystemClockNow.php delete mode 100644 implementation/11-templating/.php-cs-fixer.php delete mode 100644 implementation/11-templating/.phpcs.xml.dist delete mode 100644 implementation/11-templating/composer.json delete mode 100644 implementation/11-templating/composer.lock delete mode 100644 implementation/11-templating/config/dependencies.php delete mode 100644 implementation/11-templating/config/routes.php delete mode 100644 implementation/11-templating/config/settings.php delete mode 100644 implementation/11-templating/phpstan-baseline.neon delete mode 100644 implementation/11-templating/phpstan.neon delete mode 100644 implementation/11-templating/public/favicon.ico delete mode 100644 implementation/11-templating/public/index.php delete mode 100644 implementation/11-templating/src/.gitkeep delete mode 100644 implementation/11-templating/src/Action/Hello.php delete mode 100644 implementation/11-templating/src/Action/Other.php delete mode 100644 implementation/11-templating/src/Bootstrap.php delete mode 100644 implementation/11-templating/src/Exception/InternalServerError.php delete mode 100644 implementation/11-templating/src/Exception/MethodNotAllowed.php delete mode 100644 implementation/11-templating/src/Exception/NotFound.php delete mode 100644 implementation/11-templating/src/Service/Time/Now.php delete mode 100644 implementation/11-templating/src/Service/Time/SystemClockNow.php delete mode 100644 implementation/11-templating/src/Settings.php delete mode 100644 implementation/11-templating/src/Template/MustacheRenderer.php delete mode 100644 implementation/11-templating/src/Template/Renderer.php delete mode 100644 implementation/11-templating/templates/hello.html delete mode 100644 implementation/12-configuration/.php-cs-fixer.php delete mode 100644 implementation/12-configuration/.phpcs.xml.dist delete mode 100644 implementation/12-configuration/composer.json delete mode 100644 implementation/12-configuration/composer.lock delete mode 100644 implementation/12-configuration/config/dependencies.php delete mode 100644 implementation/12-configuration/config/routes.php delete mode 100644 implementation/12-configuration/config/settings.php delete mode 100644 implementation/12-configuration/phpstan-baseline.neon delete mode 100644 implementation/12-configuration/phpstan.neon delete mode 100644 implementation/12-configuration/public/favicon.ico delete mode 100644 implementation/12-configuration/public/index.php delete mode 100644 implementation/12-configuration/src/.gitkeep delete mode 100644 implementation/12-configuration/src/Action/Hello.php delete mode 100644 implementation/12-configuration/src/Action/Other.php delete mode 100644 implementation/12-configuration/src/Bootstrap.php delete mode 100644 implementation/12-configuration/src/Exception/InternalServerError.php delete mode 100644 implementation/12-configuration/src/Exception/MethodNotAllowed.php delete mode 100644 implementation/12-configuration/src/Exception/NotFound.php delete mode 100644 implementation/12-configuration/src/Factory/ContainerProvider.php delete mode 100644 implementation/12-configuration/src/Factory/FileSystemSettingsProvider.php delete mode 100644 implementation/12-configuration/src/Factory/SettingsContainerProvider.php delete mode 100644 implementation/12-configuration/src/Factory/SettingsProvider.php delete mode 100644 implementation/12-configuration/src/Service/Time/Now.php delete mode 100644 implementation/12-configuration/src/Service/Time/SystemClockNow.php delete mode 100644 implementation/12-configuration/src/Settings.php delete mode 100644 implementation/12-configuration/src/Template/MustacheRenderer.php delete mode 100644 implementation/12-configuration/src/Template/Renderer.php delete mode 100644 implementation/12-configuration/templates/hello.html delete mode 100644 implementation/13-refactoring/.php-cs-fixer.php delete mode 100644 implementation/13-refactoring/.phpcs.xml.dist delete mode 100644 implementation/13-refactoring/composer.json delete mode 100644 implementation/13-refactoring/composer.lock delete mode 100644 implementation/13-refactoring/config/dependencies.php delete mode 100644 implementation/13-refactoring/config/routes.php delete mode 100644 implementation/13-refactoring/config/settings.php delete mode 100644 implementation/13-refactoring/phpstan-baseline.neon delete mode 100644 implementation/13-refactoring/phpstan.neon delete mode 100644 implementation/13-refactoring/public/favicon.ico delete mode 100644 implementation/13-refactoring/public/index.php delete mode 100644 implementation/13-refactoring/src/.gitkeep delete mode 100644 implementation/13-refactoring/src/Action/Hello.php delete mode 100644 implementation/13-refactoring/src/Action/Other.php delete mode 100644 implementation/13-refactoring/src/Bootstrap.php delete mode 100644 implementation/13-refactoring/src/Exception/InternalServerError.php delete mode 100644 implementation/13-refactoring/src/Exception/MethodNotAllowed.php delete mode 100644 implementation/13-refactoring/src/Exception/NotFound.php delete mode 100644 implementation/13-refactoring/src/Factory/ContainerProvider.php delete mode 100644 implementation/13-refactoring/src/Factory/DiactorosRequestFactory.php delete mode 100644 implementation/13-refactoring/src/Factory/FileSystemSettingsProvider.php delete mode 100644 implementation/13-refactoring/src/Factory/RequestFactory.php delete mode 100644 implementation/13-refactoring/src/Factory/SettingsContainerProvider.php delete mode 100644 implementation/13-refactoring/src/Factory/SettingsProvider.php delete mode 100644 implementation/13-refactoring/src/Http/BasicEmitter.php delete mode 100644 implementation/13-refactoring/src/Http/Emitter.php delete mode 100644 implementation/13-refactoring/src/Http/InvokerRoutedHandler.php delete mode 100644 implementation/13-refactoring/src/Http/RouteMiddleware.php delete mode 100644 implementation/13-refactoring/src/Http/RoutedRequestHandler.php delete mode 100644 implementation/13-refactoring/src/Kernel.php delete mode 100644 implementation/13-refactoring/src/Service/Time/Now.php delete mode 100644 implementation/13-refactoring/src/Service/Time/SystemClockNow.php delete mode 100644 implementation/13-refactoring/src/Settings.php delete mode 100644 implementation/13-refactoring/src/Template/MustacheRenderer.php delete mode 100644 implementation/13-refactoring/src/Template/Renderer.php delete mode 100644 implementation/13-refactoring/templates/hello.html delete mode 100644 implementation/14-middleware/.php-cs-fixer.php delete mode 100644 implementation/14-middleware/.phpcs.xml.dist delete mode 100644 implementation/14-middleware/composer.json delete mode 100644 implementation/14-middleware/composer.lock delete mode 100644 implementation/14-middleware/config/dependencies.php delete mode 100644 implementation/14-middleware/config/middlewares.php delete mode 100644 implementation/14-middleware/config/routes.php delete mode 100644 implementation/14-middleware/config/settings.php delete mode 100644 implementation/14-middleware/phpstan-baseline.neon delete mode 100644 implementation/14-middleware/phpstan.neon delete mode 100644 implementation/14-middleware/public/favicon.ico delete mode 100644 implementation/14-middleware/public/index.php delete mode 100644 implementation/14-middleware/src/.gitkeep delete mode 100644 implementation/14-middleware/src/Action/Hello.php delete mode 100644 implementation/14-middleware/src/Action/Other.php delete mode 100644 implementation/14-middleware/src/Bootstrap.php delete mode 100644 implementation/14-middleware/src/Exception/InternalServerError.php delete mode 100644 implementation/14-middleware/src/Exception/MethodNotAllowed.php delete mode 100644 implementation/14-middleware/src/Exception/NotFound.php delete mode 100644 implementation/14-middleware/src/Factory/ContainerProvider.php delete mode 100644 implementation/14-middleware/src/Factory/DiactorosRequestFactory.php delete mode 100644 implementation/14-middleware/src/Factory/FileSystemSettingsProvider.php delete mode 100644 implementation/14-middleware/src/Factory/PipelineProvider.php delete mode 100644 implementation/14-middleware/src/Factory/RequestFactory.php delete mode 100644 implementation/14-middleware/src/Factory/SettingsContainerProvider.php delete mode 100644 implementation/14-middleware/src/Factory/SettingsProvider.php delete mode 100644 implementation/14-middleware/src/Http/BasicEmitter.php delete mode 100644 implementation/14-middleware/src/Http/ContainerPipeline.php delete mode 100644 implementation/14-middleware/src/Http/Emitter.php delete mode 100644 implementation/14-middleware/src/Http/InvokerRoutedHandler.php delete mode 100644 implementation/14-middleware/src/Http/Pipeline.php delete mode 100644 implementation/14-middleware/src/Http/RouteMiddleware.php delete mode 100644 implementation/14-middleware/src/Http/RoutedRequestHandler.php delete mode 100644 implementation/14-middleware/src/Kernel.php delete mode 100644 implementation/14-middleware/src/Service/Time/Now.php delete mode 100644 implementation/14-middleware/src/Service/Time/SystemClockNow.php delete mode 100644 implementation/14-middleware/src/Settings.php delete mode 100644 implementation/14-middleware/src/Template/MustacheRenderer.php delete mode 100644 implementation/14-middleware/src/Template/Renderer.php delete mode 100644 implementation/14-middleware/templates/hello.html delete mode 100644 implementation/15-adding-content/.php-cs-fixer.php delete mode 100644 implementation/15-adding-content/.phpcs.xml.dist delete mode 100644 implementation/15-adding-content/cli-config.php delete mode 100644 implementation/15-adding-content/composer.json delete mode 100644 implementation/15-adding-content/composer.lock delete mode 100644 implementation/15-adding-content/config/dependencies.php delete mode 100644 implementation/15-adding-content/config/middlewares.php delete mode 100644 implementation/15-adding-content/config/routes.php delete mode 100644 implementation/15-adding-content/config/settings.php delete mode 100644 implementation/15-adding-content/data/pages/01-front-controller.md delete mode 100644 implementation/15-adding-content/data/pages/02-composer.md delete mode 100644 implementation/15-adding-content/data/pages/03-error-handler.md delete mode 100644 implementation/15-adding-content/data/pages/04-development-helpers.md delete mode 100644 implementation/15-adding-content/data/pages/05-http.md delete mode 100644 implementation/15-adding-content/data/pages/06-router.md delete mode 100644 implementation/15-adding-content/data/pages/07-dispatching-to-a-class.md delete mode 100644 implementation/15-adding-content/data/pages/08-inversion-of-control.md delete mode 100644 implementation/15-adding-content/data/pages/09-dependency-injector.md delete mode 100644 implementation/15-adding-content/data/pages/10-invoker.md delete mode 100644 implementation/15-adding-content/data/pages/11-templating.md delete mode 100644 implementation/15-adding-content/data/pages/12-configuration.md delete mode 100644 implementation/15-adding-content/data/pages/13-refactoring.md delete mode 100644 implementation/15-adding-content/data/pages/14-middleware.md delete mode 100644 implementation/15-adding-content/phpstan-baseline.neon delete mode 100644 implementation/15-adding-content/phpstan.neon delete mode 100644 implementation/15-adding-content/public/css/spectre-exp.min.css delete mode 100644 implementation/15-adding-content/public/css/spectre-icons.min.css delete mode 100644 implementation/15-adding-content/public/css/spectre.min.css delete mode 100644 implementation/15-adding-content/public/favicon.ico delete mode 100644 implementation/15-adding-content/public/index.php delete mode 100644 implementation/15-adding-content/src/.gitkeep delete mode 100644 implementation/15-adding-content/src/Action/Hello.php delete mode 100644 implementation/15-adding-content/src/Action/Other.php delete mode 100644 implementation/15-adding-content/src/Action/Page.php delete mode 100644 implementation/15-adding-content/src/Bootstrap.php delete mode 100644 implementation/15-adding-content/src/Exception/InternalServerError.php delete mode 100644 implementation/15-adding-content/src/Exception/MethodNotAllowed.php delete mode 100644 implementation/15-adding-content/src/Exception/NotFound.php delete mode 100644 implementation/15-adding-content/src/Factory/ContainerProvider.php delete mode 100644 implementation/15-adding-content/src/Factory/DiactorosRequestFactory.php delete mode 100644 implementation/15-adding-content/src/Factory/DoctrineEm.php delete mode 100644 implementation/15-adding-content/src/Factory/FileSystemSettingsProvider.php delete mode 100644 implementation/15-adding-content/src/Factory/PipelineProvider.php delete mode 100644 implementation/15-adding-content/src/Factory/RequestFactory.php delete mode 100644 implementation/15-adding-content/src/Factory/SettingsContainerProvider.php delete mode 100644 implementation/15-adding-content/src/Factory/SettingsProvider.php delete mode 100644 implementation/15-adding-content/src/Http/BasicEmitter.php delete mode 100644 implementation/15-adding-content/src/Http/ContainerPipeline.php delete mode 100644 implementation/15-adding-content/src/Http/Emitter.php delete mode 100644 implementation/15-adding-content/src/Http/InvokerRoutedHandler.php delete mode 100644 implementation/15-adding-content/src/Http/Pipeline.php delete mode 100644 implementation/15-adding-content/src/Http/RouteMiddleware.php delete mode 100644 implementation/15-adding-content/src/Http/RoutedRequestHandler.php delete mode 100644 implementation/15-adding-content/src/Kernel.php delete mode 100644 implementation/15-adding-content/src/Middleware/CacheMiddleware.php delete mode 100644 implementation/15-adding-content/src/Model/MarkdownPage.php delete mode 100644 implementation/15-adding-content/src/Repository/CachedMarkdownPageRepo.php delete mode 100644 implementation/15-adding-content/src/Repository/DoctrineMarkdownPageRepo.php delete mode 100644 implementation/15-adding-content/src/Repository/MarkdownPageFilesystem.php delete mode 100644 implementation/15-adding-content/src/Repository/MarkdownPageRepo.php delete mode 100644 implementation/15-adding-content/src/Service/Time/Now.php delete mode 100644 implementation/15-adding-content/src/Service/Time/SystemClockNow.php delete mode 100644 implementation/15-adding-content/src/Settings.php delete mode 100644 implementation/15-adding-content/src/Template/GithubMarkdownRenderer.php delete mode 100644 implementation/15-adding-content/src/Template/MarkdownParser.php delete mode 100644 implementation/15-adding-content/src/Template/MustacheRenderer.php delete mode 100644 implementation/15-adding-content/src/Template/ParsedownParser.php delete mode 100644 implementation/15-adding-content/src/Template/Renderer.php delete mode 100644 implementation/15-adding-content/templates/hello.html delete mode 100644 implementation/15-adding-content/templates/page.html delete mode 100644 implementation/15-adding-content/templates/page/list.html delete mode 100644 implementation/15-adding-content/templates/page/show.html delete mode 100644 implementation/15-adding-content/templates/pagelist.html delete mode 100644 implementation/15-adding-content/templates/partials/foot.html delete mode 100644 implementation/15-adding-content/templates/partials/head.html delete mode 100644 implementation/16-caching/.php-cs-fixer.php delete mode 100644 implementation/16-caching/.phpcs.xml.dist delete mode 100644 implementation/16-caching/composer.json delete mode 100644 implementation/16-caching/composer.lock delete mode 100644 implementation/16-caching/config/dependencies.php delete mode 100644 implementation/16-caching/config/middlewares.php delete mode 100644 implementation/16-caching/config/routes.php delete mode 100644 implementation/16-caching/config/settings.php delete mode 100644 implementation/16-caching/data/pages/01-front-controller.md delete mode 100644 implementation/16-caching/data/pages/02-composer.md delete mode 100644 implementation/16-caching/data/pages/03-error-handler.md delete mode 100644 implementation/16-caching/data/pages/04-development-helpers.md delete mode 100644 implementation/16-caching/data/pages/05-http.md delete mode 100644 implementation/16-caching/data/pages/06-router.md delete mode 100644 implementation/16-caching/data/pages/07-dispatching-to-a-class.md delete mode 100644 implementation/16-caching/data/pages/08-inversion-of-control.md delete mode 100644 implementation/16-caching/data/pages/09-dependency-injector.md delete mode 100644 implementation/16-caching/data/pages/10-invoker.md delete mode 100644 implementation/16-caching/data/pages/11-templating.md delete mode 100644 implementation/16-caching/data/pages/12-configuration.md delete mode 100644 implementation/16-caching/data/pages/13-refactoring.md delete mode 100644 implementation/16-caching/data/pages/14-middleware.md delete mode 100644 implementation/16-caching/phpstan-baseline.neon delete mode 100644 implementation/16-caching/phpstan.neon delete mode 100644 implementation/16-caching/public/css/spectre-exp.min.css delete mode 100644 implementation/16-caching/public/css/spectre-icons.min.css delete mode 100644 implementation/16-caching/public/css/spectre.min.css delete mode 100644 implementation/16-caching/public/favicon.ico delete mode 100644 implementation/16-caching/public/index.php delete mode 100644 implementation/16-caching/src/.gitkeep delete mode 100644 implementation/16-caching/src/Action/Hello.php delete mode 100644 implementation/16-caching/src/Action/Other.php delete mode 100644 implementation/16-caching/src/Action/Page.php delete mode 100644 implementation/16-caching/src/Bootstrap.php delete mode 100644 implementation/16-caching/src/Exception/InternalServerError.php delete mode 100644 implementation/16-caching/src/Exception/MethodNotAllowed.php delete mode 100644 implementation/16-caching/src/Exception/NotFound.php delete mode 100644 implementation/16-caching/src/Factory/ContainerProvider.php delete mode 100644 implementation/16-caching/src/Factory/DiactorosRequestFactory.php delete mode 100644 implementation/16-caching/src/Factory/FileSystemSettingsProvider.php delete mode 100644 implementation/16-caching/src/Factory/PipelineProvider.php delete mode 100644 implementation/16-caching/src/Factory/RequestFactory.php delete mode 100644 implementation/16-caching/src/Factory/SettingsContainerProvider.php delete mode 100644 implementation/16-caching/src/Factory/SettingsProvider.php delete mode 100644 implementation/16-caching/src/Http/BasicEmitter.php delete mode 100644 implementation/16-caching/src/Http/ContainerPipeline.php delete mode 100644 implementation/16-caching/src/Http/Emitter.php delete mode 100644 implementation/16-caching/src/Http/InvokerRoutedHandler.php delete mode 100644 implementation/16-caching/src/Http/Pipeline.php delete mode 100644 implementation/16-caching/src/Http/RouteMiddleware.php delete mode 100644 implementation/16-caching/src/Http/RoutedRequestHandler.php delete mode 100644 implementation/16-caching/src/Kernel.php delete mode 100644 implementation/16-caching/src/Middleware/CacheMiddleware.php delete mode 100644 implementation/16-caching/src/Model/MarkdownPage.php delete mode 100644 implementation/16-caching/src/Repository/CachedMarkdownPageRepo.php delete mode 100644 implementation/16-caching/src/Repository/MarkdownPageFilesystem.php delete mode 100644 implementation/16-caching/src/Repository/MarkdownPageRepo.php delete mode 100644 implementation/16-caching/src/Service/Time/Now.php delete mode 100644 implementation/16-caching/src/Service/Time/SystemClockNow.php delete mode 100644 implementation/16-caching/src/Settings.php delete mode 100644 implementation/16-caching/src/Template/MustacheRenderer.php delete mode 100644 implementation/16-caching/src/Template/Renderer.php delete mode 100644 implementation/16-caching/templates/hello.html delete mode 100644 implementation/16-caching/templates/page.html delete mode 100644 implementation/16-caching/templates/partials/foot.html delete mode 100644 implementation/16-caching/templates/partials/head.html delete mode 100644 implementation/16-data-repository/.php-cs-fixer.php delete mode 100644 implementation/16-data-repository/.phpcs.xml.dist delete mode 100644 implementation/16-data-repository/cli-config.php delete mode 100644 implementation/16-data-repository/composer.json delete mode 100644 implementation/16-data-repository/composer.lock delete mode 100644 implementation/16-data-repository/config/dependencies.php delete mode 100644 implementation/16-data-repository/config/middlewares.php delete mode 100644 implementation/16-data-repository/config/routes.php delete mode 100644 implementation/16-data-repository/config/settings.php delete mode 100644 implementation/16-data-repository/data/pages/01-front-controller.md delete mode 100644 implementation/16-data-repository/data/pages/02-composer.md delete mode 100644 implementation/16-data-repository/data/pages/03-error-handler.md delete mode 100644 implementation/16-data-repository/data/pages/04-development-helpers.md delete mode 100644 implementation/16-data-repository/data/pages/05-http.md delete mode 100644 implementation/16-data-repository/data/pages/06-router.md delete mode 100644 implementation/16-data-repository/data/pages/07-dispatching-to-a-class.md delete mode 100644 implementation/16-data-repository/data/pages/08-inversion-of-control.md delete mode 100644 implementation/16-data-repository/data/pages/09-dependency-injector.md delete mode 100644 implementation/16-data-repository/data/pages/10-invoker.md delete mode 100644 implementation/16-data-repository/data/pages/11-templating.md delete mode 100644 implementation/16-data-repository/data/pages/12-configuration.md delete mode 100644 implementation/16-data-repository/data/pages/13-refactoring.md delete mode 100644 implementation/16-data-repository/data/pages/14-middleware.md delete mode 100644 implementation/16-data-repository/phpstan-baseline.neon delete mode 100644 implementation/16-data-repository/phpstan.neon delete mode 100644 implementation/16-data-repository/public/css/spectre-exp.min.css delete mode 100644 implementation/16-data-repository/public/css/spectre-icons.min.css delete mode 100644 implementation/16-data-repository/public/css/spectre.min.css delete mode 100644 implementation/16-data-repository/public/favicon.ico delete mode 100644 implementation/16-data-repository/public/index.php delete mode 100644 implementation/16-data-repository/src/.gitkeep delete mode 100644 implementation/16-data-repository/src/Action/Hello.php delete mode 100644 implementation/16-data-repository/src/Action/Other.php delete mode 100644 implementation/16-data-repository/src/Action/Page.php delete mode 100644 implementation/16-data-repository/src/Bootstrap.php delete mode 100644 implementation/16-data-repository/src/Exception/InternalServerError.php delete mode 100644 implementation/16-data-repository/src/Exception/MethodNotAllowed.php delete mode 100644 implementation/16-data-repository/src/Exception/NotFound.php delete mode 100644 implementation/16-data-repository/src/Factory/ContainerProvider.php delete mode 100644 implementation/16-data-repository/src/Factory/DiactorosRequestFactory.php delete mode 100644 implementation/16-data-repository/src/Factory/DoctrineEm.php delete mode 100644 implementation/16-data-repository/src/Factory/FileSystemSettingsProvider.php delete mode 100644 implementation/16-data-repository/src/Factory/PipelineProvider.php delete mode 100644 implementation/16-data-repository/src/Factory/RequestFactory.php delete mode 100644 implementation/16-data-repository/src/Factory/SettingsContainerProvider.php delete mode 100644 implementation/16-data-repository/src/Factory/SettingsProvider.php delete mode 100644 implementation/16-data-repository/src/Http/BasicEmitter.php delete mode 100644 implementation/16-data-repository/src/Http/ContainerPipeline.php delete mode 100644 implementation/16-data-repository/src/Http/Emitter.php delete mode 100644 implementation/16-data-repository/src/Http/InvokerRoutedHandler.php delete mode 100644 implementation/16-data-repository/src/Http/Pipeline.php delete mode 100644 implementation/16-data-repository/src/Http/RouteMiddleware.php delete mode 100644 implementation/16-data-repository/src/Http/RoutedRequestHandler.php delete mode 100644 implementation/16-data-repository/src/Kernel.php delete mode 100644 implementation/16-data-repository/src/Model/MarkdownPage.php delete mode 100644 implementation/16-data-repository/src/Repository/FileSystemMarkdownPageRepo.php delete mode 100644 implementation/16-data-repository/src/Repository/MarkdownPageRepo.php delete mode 100644 implementation/16-data-repository/src/Service/Time/Now.php delete mode 100644 implementation/16-data-repository/src/Service/Time/SystemClockNow.php delete mode 100644 implementation/16-data-repository/src/Settings.php delete mode 100644 implementation/16-data-repository/src/Template/MarkdownParser.php delete mode 100644 implementation/16-data-repository/src/Template/MustacheRenderer.php delete mode 100644 implementation/16-data-repository/src/Template/ParsedownParser.php delete mode 100644 implementation/16-data-repository/src/Template/Renderer.php delete mode 100644 implementation/16-data-repository/templates/hello.html delete mode 100644 implementation/16-data-repository/templates/page.html delete mode 100644 implementation/16-data-repository/templates/page/list.html delete mode 100644 implementation/16-data-repository/templates/page/show.html delete mode 100644 implementation/16-data-repository/templates/pagelist.html delete mode 100644 implementation/16-data-repository/templates/partials/foot.html delete mode 100644 implementation/16-data-repository/templates/partials/head.html delete mode 100644 implementation/18-caching/.php-cs-fixer.php delete mode 100644 implementation/18-caching/.phpcs.xml.dist delete mode 100644 implementation/18-caching/cli-config.php delete mode 100644 implementation/18-caching/composer.json delete mode 100644 implementation/18-caching/composer.lock delete mode 100644 implementation/18-caching/config/dependencies.php delete mode 100644 implementation/18-caching/config/middlewares.php delete mode 100644 implementation/18-caching/config/routes.php delete mode 100644 implementation/18-caching/config/settings.php delete mode 100644 implementation/18-caching/data/pages/01-front-controller.md delete mode 100644 implementation/18-caching/data/pages/02-composer.md delete mode 100644 implementation/18-caching/data/pages/03-error-handler.md delete mode 100644 implementation/18-caching/data/pages/04-development-helpers.md delete mode 100644 implementation/18-caching/data/pages/05-http.md delete mode 100644 implementation/18-caching/data/pages/06-router.md delete mode 100644 implementation/18-caching/data/pages/07-dispatching-to-a-class.md delete mode 100644 implementation/18-caching/data/pages/08-inversion-of-control.md delete mode 100644 implementation/18-caching/data/pages/09-dependency-injector.md delete mode 100644 implementation/18-caching/data/pages/10-invoker.md delete mode 100644 implementation/18-caching/data/pages/11-templating.md delete mode 100644 implementation/18-caching/data/pages/12-configuration.md delete mode 100644 implementation/18-caching/data/pages/13-refactoring.md delete mode 100644 implementation/18-caching/data/pages/14-middleware.md delete mode 100644 implementation/18-caching/data/pages/15-adding-content.md delete mode 100644 implementation/18-caching/data/pages/16-data-repository.md delete mode 100644 implementation/18-caching/data/pages/17-performance.md delete mode 100644 implementation/18-caching/data/pages/18-caching.md delete mode 100644 implementation/18-caching/phpstan-baseline.neon delete mode 100644 implementation/18-caching/phpstan.neon delete mode 100644 implementation/18-caching/public/css/spectre-exp.min.css delete mode 100644 implementation/18-caching/public/css/spectre-icons.min.css delete mode 100644 implementation/18-caching/public/css/spectre.min.css delete mode 100644 implementation/18-caching/public/favicon.ico delete mode 100644 implementation/18-caching/public/index.php delete mode 100644 implementation/18-caching/src/.gitkeep delete mode 100644 implementation/18-caching/src/Action/Hello.php delete mode 100644 implementation/18-caching/src/Action/Other.php delete mode 100644 implementation/18-caching/src/Action/Page.php delete mode 100644 implementation/18-caching/src/Bootstrap.php delete mode 100644 implementation/18-caching/src/Exception/InternalServerError.php delete mode 100644 implementation/18-caching/src/Exception/MethodNotAllowed.php delete mode 100644 implementation/18-caching/src/Exception/NotFound.php delete mode 100644 implementation/18-caching/src/Factory/ContainerProvider.php delete mode 100644 implementation/18-caching/src/Factory/DiactorosRequestFactory.php delete mode 100644 implementation/18-caching/src/Factory/FileSystemSettingsProvider.php delete mode 100644 implementation/18-caching/src/Factory/PipelineProvider.php delete mode 100644 implementation/18-caching/src/Factory/RequestFactory.php delete mode 100644 implementation/18-caching/src/Factory/SettingsContainerProvider.php delete mode 100644 implementation/18-caching/src/Factory/SettingsProvider.php delete mode 100644 implementation/18-caching/src/Http/BasicEmitter.php delete mode 100644 implementation/18-caching/src/Http/ContainerPipeline.php delete mode 100644 implementation/18-caching/src/Http/Emitter.php delete mode 100644 implementation/18-caching/src/Http/InvokerRoutedHandler.php delete mode 100644 implementation/18-caching/src/Http/Pipeline.php delete mode 100644 implementation/18-caching/src/Http/RouteMiddleware.php delete mode 100644 implementation/18-caching/src/Http/RoutedRequestHandler.php delete mode 100644 implementation/18-caching/src/Kernel.php delete mode 100644 implementation/18-caching/src/Middleware/Cache.php delete mode 100644 implementation/18-caching/src/Model/MarkdownPage.php delete mode 100644 implementation/18-caching/src/Repository/CachedMarkdownPageRepo.php delete mode 100644 implementation/18-caching/src/Repository/FileSystemMarkdownPageRepo.php delete mode 100644 implementation/18-caching/src/Repository/MarkdownPageRepo.php delete mode 100644 implementation/18-caching/src/Service/Cache/ApcuCache.php delete mode 100644 implementation/18-caching/src/Service/Cache/EasyCache.php delete mode 100644 implementation/18-caching/src/Service/Time/Now.php delete mode 100644 implementation/18-caching/src/Service/Time/SystemClockNow.php delete mode 100644 implementation/18-caching/src/Settings.php delete mode 100644 implementation/18-caching/src/Template/MarkdownParser.php delete mode 100644 implementation/18-caching/src/Template/MustacheRenderer.php delete mode 100644 implementation/18-caching/src/Template/ParsedownParser.php delete mode 100644 implementation/18-caching/src/Template/Renderer.php delete mode 100644 implementation/18-caching/templates/hello.html delete mode 100644 implementation/18-caching/templates/page.html delete mode 100644 implementation/18-caching/templates/page/list.html delete mode 100644 implementation/18-caching/templates/page/show.html delete mode 100644 implementation/18-caching/templates/pagelist.html delete mode 100644 implementation/18-caching/templates/partials/foot.html delete mode 100644 implementation/18-caching/templates/partials/head.html delete mode 100644 to-be-continued.md diff --git a/app/.php-cs-fixer.php b/app/.php-cs-fixer.php deleted file mode 100644 index 705a7d7..0000000 --- a/app/.php-cs-fixer.php +++ /dev/null @@ -1,38 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config' - ]) - ); \ No newline at end of file diff --git a/app/.phpcs.xml.dist b/app/.phpcs.xml.dist deleted file mode 100644 index 3b433f6..0000000 --- a/app/.phpcs.xml.dist +++ /dev/null @@ -1,9 +0,0 @@ - - - - - src - config - - - \ No newline at end of file diff --git a/app/cli-config.php b/app/cli-config.php deleted file mode 100644 index fbc6598..0000000 --- a/app/cli-config.php +++ /dev/null @@ -1,13 +0,0 @@ -getContainer(); - -return ConsoleRunner::createHelperSet($container->get(EntityManagerInterface::class)); diff --git a/app/composer.json b/app/composer.json deleted file mode 100644 index 29695da..0000000 --- a/app/composer.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3", - "mustache/mustache": "^2.14", - "psr/http-server-middleware": "^1.0", - "middlewares/trailing-slash": "^2.0", - "middlewares/whoops": "^2.0", - "erusev/parsedown": "^1.7", - "league/commonmark": "^2.2", - "ext-apcu": "*", - "ext-zend-opcache": "*" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require-dev": { - "phpstan/phpstan": "^1.5", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0", - "squizlabs/php_codesniffer": "^3.6", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1", - "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0" - }, - "config": { - "optimize-autoloader": true, - "allow-plugins": { - "phpstan/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "serve": [ - "Composer\\Config::disableProcessTimeout", - "php -S 0.0.0.0:1234 -t public" - ], - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" - } -} diff --git a/app/composer.lock b/app/composer.lock deleted file mode 100644 index 40cd7d3..0000000 --- a/app/composer.lock +++ /dev/null @@ -1,2440 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "9a29468fd456190a9fbcff98ed42d862", - "packages": [ - { - "name": "dflydev/dot-access-data", - "version": "v3.0.1", - "source": { - "type": "git", - "url": "https://github.com/dflydev/dflydev-dot-access-data.git", - "reference": "0992cc19268b259a39e86f296da5f0677841f42c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", - "reference": "0992cc19268b259a39e86f296da5f0677841f42c", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.42", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", - "scrutinizer/ocular": "1.6.0", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^3.14" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Dflydev\\DotAccessData\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dragonfly Development Inc.", - "email": "info@dflydev.com", - "homepage": "http://dflydev.com" - }, - { - "name": "Beau Simensen", - "email": "beau@dflydev.com", - "homepage": "http://beausimensen.com" - }, - { - "name": "Carlos Frutos", - "email": "carlos@kiwing.it", - "homepage": "https://github.com/cfrutos" - }, - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com" - } - ], - "description": "Given a deep data structure, access data by dot notation.", - "homepage": "https://github.com/dflydev/dflydev-dot-access-data", - "keywords": [ - "access", - "data", - "dot", - "notation" - ], - "support": { - "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", - "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" - }, - "time": "2021-08-13T13:06:58+00:00" - }, - { - "name": "erusev/parsedown", - "version": "1.7.4", - "source": { - "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35" - }, - "type": "library", - "autoload": { - "psr-0": { - "Parsedown": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com" - } - ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org", - "keywords": [ - "markdown", - "parser" - ], - "support": { - "issues": "https://github.com/erusev/parsedown/issues", - "source": "https://github.com/erusev/parsedown/tree/1.7.x" - }, - "time": "2019-12-30T22:54:17+00:00" - }, - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.9.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2022-03-29T20:12:16+00:00" - }, - { - "name": "league/commonmark", - "version": "2.2.3", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/commonmark.git", - "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", - "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "league/config": "^1.1.1", - "php": "^7.4 || ^8.0", - "psr/event-dispatcher": "^1.0", - "symfony/deprecation-contracts": "^2.1 || ^3.0", - "symfony/polyfill-php80": "^1.15" - }, - "require-dev": { - "cebe/markdown": "^1.0", - "commonmark/cmark": "0.30.0", - "commonmark/commonmark.js": "0.30.0", - "composer/package-versions-deprecated": "^1.8", - "erusev/parsedown": "^1.0", - "ext-json": "*", - "github/gfm": "0.29.0", - "michelf/php-markdown": "^1.4", - "phpstan/phpstan": "^0.12.88 || ^1.0.0", - "phpunit/phpunit": "^9.5.5", - "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", - "unleashedtech/php-coding-standard": "^3.1", - "vimeo/psalm": "^4.7.3" - }, - "suggest": { - "symfony/yaml": "v2.3+ required if using the Front Matter extension" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.3-dev" - } - }, - "autoload": { - "psr-4": { - "League\\CommonMark\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com", - "role": "Lead Developer" - } - ], - "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", - "homepage": "https://commonmark.thephpleague.com", - "keywords": [ - "commonmark", - "flavored", - "gfm", - "github", - "github-flavored", - "markdown", - "md", - "parser" - ], - "support": { - "docs": "https://commonmark.thephpleague.com/", - "forum": "https://github.com/thephpleague/commonmark/discussions", - "issues": "https://github.com/thephpleague/commonmark/issues", - "rss": "https://github.com/thephpleague/commonmark/releases.atom", - "source": "https://github.com/thephpleague/commonmark" - }, - "funding": [ - { - "url": "https://www.colinodell.com/sponsor", - "type": "custom" - }, - { - "url": "https://www.paypal.me/colinpodell/10.00", - "type": "custom" - }, - { - "url": "https://github.com/colinodell", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/commonmark", - "type": "tidelift" - } - ], - "time": "2022-02-26T21:24:45+00:00" - }, - { - "name": "league/config", - "version": "v1.1.1", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/config.git", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "shasum": "" - }, - "require": { - "dflydev/dot-access-data": "^3.0.1", - "nette/schema": "^1.2", - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.90", - "phpunit/phpunit": "^9.5.5", - "scrutinizer/ocular": "^1.8.1", - "unleashedtech/php-coding-standard": "^3.1", - "vimeo/psalm": "^4.7.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.2-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Config\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com", - "role": "Lead Developer" - } - ], - "description": "Define configuration arrays with strict schemas and access values with dot notation", - "homepage": "https://config.thephpleague.com", - "keywords": [ - "array", - "config", - "configuration", - "dot", - "dot-access", - "nested", - "schema" - ], - "support": { - "docs": "https://config.thephpleague.com/", - "issues": "https://github.com/thephpleague/config/issues", - "rss": "https://github.com/thephpleague/config/releases.atom", - "source": "https://github.com/thephpleague/config" - }, - "funding": [ - { - "url": "https://www.colinodell.com/sponsor", - "type": "custom" - }, - { - "url": "https://www.paypal.me/colinpodell/10.00", - "type": "custom" - }, - { - "url": "https://github.com/colinodell", - "type": "github" - } - ], - "time": "2021-08-14T12:15:32+00:00" - }, - { - "name": "middlewares/trailing-slash", - "version": "v2.0.1", - "source": { - "type": "git", - "url": "https://github.com/middlewares/trailing-slash.git", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", - "shasum": "" - }, - "require": { - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to normalize the trailing slash of the uri path", - "homepage": "https://github.com/middlewares/trailing-slash", - "keywords": [ - "http", - "middleware", - "normalize", - "path", - "psr-15", - "psr-7", - "slash" - ], - "support": { - "issues": "https://github.com/middlewares/trailing-slash/issues", - "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" - }, - "time": "2020-12-02T00:06:55+00:00" - }, - { - "name": "middlewares/utils", - "version": "v3.3.0", - "source": { - "type": "git", - "url": "https://github.com/middlewares/utils.git", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^v2.16", - "guzzlehttp/psr7": "^2.0", - "laminas/laminas-diactoros": "^2.4", - "nyholm/psr7": "^1.0", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "slim/psr7": "^1.4", - "squizlabs/php_codesniffer": "^3.5", - "sunrise/http-message": "^1.0", - "sunrise/http-server-request": "^1.0", - "sunrise/stream": "^1.0.15", - "sunrise/uri": "^1.0.15" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\Utils\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Common utils for PSR-15 middleware packages", - "homepage": "https://github.com/middlewares/utils", - "keywords": [ - "PSR-11", - "http", - "middleware", - "psr-15", - "psr-17", - "psr-7" - ], - "support": { - "issues": "https://github.com/middlewares/utils/issues", - "source": "https://github.com/middlewares/utils/tree/v3.3.0" - }, - "time": "2021-07-04T17:56:23+00:00" - }, - { - "name": "middlewares/whoops", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/middlewares/whoops.git", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", - "shasum": "" - }, - "require": { - "filp/whoops": "^2.5", - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "eloquent/phony-phpunit": "^5.0 || ^7.0", - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to use Whoops as error handler", - "homepage": "https://github.com/middlewares/whoops", - "keywords": [ - "error", - "http", - "middleware", - "psr-15", - "psr-7", - "server", - "whoops" - ], - "support": { - "issues": "https://github.com/middlewares/whoops/issues", - "source": "https://github.com/middlewares/whoops/tree/v2.0.2" - }, - "time": "2022-01-27T20:31:30+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nette/schema", - "version": "v1.2.2", - "source": { - "type": "git", - "url": "https://github.com/nette/schema.git", - "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", - "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", - "shasum": "" - }, - "require": { - "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": ">=7.1 <8.2" - }, - "require-dev": { - "nette/tester": "^2.3 || ^2.4", - "phpstan/phpstan-nette": "^0.12", - "tracy/tracy": "^2.7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "📐 Nette Schema: validating data structures against a given Schema.", - "homepage": "https://nette.org", - "keywords": [ - "config", - "nette" - ], - "support": { - "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.2" - }, - "time": "2021-10-15T11:40:02+00:00" - }, - { - "name": "nette/utils", - "version": "v3.2.7", - "source": { - "type": "git", - "url": "https://github.com/nette/utils.git", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", - "shasum": "" - }, - "require": { - "php": ">=7.2 <8.2" - }, - "conflict": { - "nette/di": "<3.0.6" - }, - "require-dev": { - "nette/tester": "~2.0", - "phpstan/phpstan": "^1.0", - "tracy/tracy": "^2.3" - }, - "suggest": { - "ext-gd": "to use Image", - "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", - "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", - "ext-json": "to use Nette\\Utils\\Json", - "ext-mbstring": "to use Strings::lower() etc...", - "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", - "ext-xml": "to use Strings::length() etc. when mbstring is not available" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", - "homepage": "https://nette.org", - "keywords": [ - "array", - "core", - "datetime", - "images", - "json", - "nette", - "paginator", - "password", - "slugify", - "string", - "unicode", - "utf-8", - "utility", - "validation" - ], - "support": { - "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.2.7" - }, - "time": "2022-01-24T11:29:14+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/http-server-middleware", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-middleware.git", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0", - "psr/http-server-handler": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side middleware", - "keywords": [ - "http", - "http-interop", - "middleware", - "psr", - "psr-15", - "psr-7", - "request", - "response" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-middleware/issues", - "source": "https://github.com/php-fig/http-server-middleware/tree/master" - }, - "time": "2018-10-30T17:12:04+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.0.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "shasum": "" - }, - "require": { - "php": ">=8.0.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:55:41+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-04T08:16:47+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "doctrine/coding-standard", - "version": "8.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/coding-standard.git", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "slevomat/coding-standard": "^6.4.1", - "squizlabs/php_codesniffer": "^3.5.8" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Steve Müller", - "email": "st.mueller@dzh-online.de" - } - ], - "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", - "keywords": [ - "checks", - "code", - "coding", - "cs", - "doctrine", - "rules", - "sniffer", - "sniffs", - "standard", - "style" - ], - "support": { - "issues": "https://github.com/doctrine/coding-standard/issues", - "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" - }, - "time": "2021-04-03T10:54:55+00:00" - }, - { - "name": "mnapoli/hard-mode", - "version": "0.3.0", - "source": { - "type": "git", - "url": "https://github.com/mnapoli/hard-mode.git", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", - "shasum": "" - }, - "require": { - "doctrine/coding-standard": "^8.0" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Hard mode for PHP", - "support": { - "issues": "https://github.com/mnapoli/hard-mode/issues", - "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" - }, - "time": "2020-10-12T07:54:37+00:00" - }, - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.9", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.26", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/master" - }, - "time": "2020-08-03T20:32:43+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.5.4", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", - "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.4" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-04-03T12:39:00+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^1.2.0" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" - }, - "time": "2021-11-18T09:30:29+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "6.4.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpstan/phpstan": "0.12.48", - "phpstan/phpstan-deprecation-rules": "0.12.5", - "phpstan/phpstan-phpunit": "0.12.16", - "phpstan/phpstan-strict-rules": "0.12.5", - "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2020-10-05T12:39:37+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1", - "ext-apcu": "*", - "ext-zend-opcache": "*" - }, - "platform-dev": [], - "plugin-api-version": "2.3.0" -} diff --git a/app/config/dependencies.php b/app/config/dependencies.php deleted file mode 100644 index df815c6..0000000 --- a/app/config/dependencies.php +++ /dev/null @@ -1,58 +0,0 @@ - fn (SystemClockNow $n) => $n, - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, - Emitter::class => fn (BasicEmitter $e) => $e, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, - MarkdownParser::class => fn (ParsedownParser $p) => $p, - MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, - EasyCache::class => fn (ApcuCache $c) => $c, - CachedMarkdownPageRepo::class => fn (EasyCache $c, FileSystemMarkdownPageRepo $r) => new CachedMarkdownPageRepo($c, $r), - - - // Factories - ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), - Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), -]; diff --git a/app/config/middlewares.php b/app/config/middlewares.php deleted file mode 100644 index ab662be..0000000 --- a/app/config/middlewares.php +++ /dev/null @@ -1,13 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/page', [Page::class, 'list']); - $r->addRoute('GET', '/page/{page}', [Page::class, 'show']); - $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); - $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -}; diff --git a/app/config/settings.php b/app/config/settings.php deleted file mode 100644 index c654565..0000000 --- a/app/config/settings.php +++ /dev/null @@ -1,12 +0,0 @@ ->](02-composer.md) - -### Front Controller - -A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. - -To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. - -A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. - -The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. - -But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. - -So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. - -Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: - -```php ->](02-composer.md) diff --git a/app/data/pages/02-composer.md b/app/data/pages/02-composer.md deleted file mode 100644 index a25a4a8..0000000 --- a/app/data/pages/02-composer.md +++ /dev/null @@ -1,75 +0,0 @@ -[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) - -### Composer - -[Composer](https://getcomposer.org/) is a dependency manager for PHP. - -Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do -something. With Composer, you can install third-party libraries for your application. - -If you don't have Composer installed already, head over to the website and install it. You can find Composer packages -for your project on [Packagist](https://packagist.org/). - -Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will -be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. - -Add the following content to the file: - -```json -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubiana", - "email": "lubiana@hannover.ccc.de" - } - ] -} -``` - -In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use -whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. -Just replace it with your namespace in your own code. - -I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. - -As the Bootstrap.php file is placed in that directory we should -add the namespace to the File as well. Here is my current Bootstrap.php -as a reference: - -```php ->](03-error-handler.md) diff --git a/app/data/pages/03-error-handler.md b/app/data/pages/03-error-handler.md deleted file mode 100644 index 60465d0..0000000 --- a/app/data/pages/03-error-handler.md +++ /dev/null @@ -1,79 +0,0 @@ -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) - -### Error Handler - -An error handler allows you to customize what happens if your code results in an error. - -A nice error page with a lot of information for debugging goes a long way during development. So the first package -for your application will take care of that. - -I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. -If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, -you have total control over your project. - -An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) - -To install a new package, open up your `composer.json` and add the package to the require part. It should now look -like this: - -```php -"require": { - "php": ">=8.1.0", - "filp/whoops": "^2.14" -}, -``` - -Now run `composer update` in your console and it will be installed. - -Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, -i that case composer automatically installs the package and updates your composer.json-file. - -But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, -ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you -only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. - -**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message -can help someone to gain access to your system. Always show a user friendly error page instead and send an email to -yourself, write to a log or something similar. So only you can see the errors in the production environment. - -For development that does not make sense though -- you want a nice error page. The solution is to have an environment -switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in -case no environment has been set. - -Then after the error handler registration, throw an `Exception` to test if everything is working correctly. -Your `Bootstrap.php` should now look similar to this: - -```php -pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (\Throwable $e) { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -throw new \Exception("Ooooopsie"); - -``` - -You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until -you get it working. Now would also be a good time for another commit. - - -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/app/data/pages/04-development-helpers.md b/app/data/pages/04-development-helpers.md deleted file mode 100644 index 74f913c..0000000 --- a/app/data/pages/04-development-helpers.md +++ /dev/null @@ -1,260 +0,0 @@ -[<< previous](03-error-handler.md) | [next >>](05-http.md) - -### Development Helpers - -I have added some more helpers to my composer.json that help me with development. As these are scripts and programms -used only for development they should not be used in a production environment. Composer has a specific sections in its -file called "dev-dependencies", everything that is required in this section does not get installen in production. - -Let's install our dev-helpers and i will explain them one by one: -`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` - -#### Static Code Analysis with phpstan - -Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or -create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements -that are not (or not always) available, if-statements that always are true and a lot of other stuff. - -A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. - -```php -/** - * @param \DateTime $date - * @return void - */ -function printDate($date) { - $date->format('Y-m-d H:i:s'); -} - -printDate('now'); -``` -if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` - -It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has -no use, and secondly, that we are calling the function with a string instead of a datetime object. - -```shell -1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - ------ --------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ --------------------------------------------------------------------------------------------- -30 Call to method DateTime::format() on a separate line has no effect. -33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. - ------ --------------------------------------------------------------------------------------------- -``` - -The second error is something that "declare strict-types" already catches for us, but the first error is something that -we usually would not discover easily without speccially looking for this errortype. - -We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and -path everytime we want to check our code for errors: - -```yaml -parameters: - level: max - paths: - - src -``` -now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project - -With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us -some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. - -```shell -composer require --dev phpstan/extension-installer -composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules -``` - -During the first install you need to allow the extension installer to actually install the extension. The second command -installs some more strict rulesets and activates them in phpstan. - -If we now rerun phpstan it already tells us about some errors we have made: - -``` - ------ ----------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ ----------------------------------------------------------------------------------------------- -10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider - using long ternary. -25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: - http://bit.ly/subtypeexception -26 Unreachable statement - code above always terminates. - ------ ----------------------------------------------------------------------------------------------- -``` - -The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove -that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific -problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working -on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages -everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we -are adding to our code. - -In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the -phpstan.neon file and run phpstan with the -'--generate-baseline' option: - -```yaml -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` -```shell -[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline -Note: Using configuration file /home/vagrant/app/phpstan.neon. - 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - - - [OK] Baseline generated with 1 error. - - -``` - -you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) - -#### PHP-CS-Fixer - -Another great tool is the php-cs-fixer, which just applies a specific style to your code. - -when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current -directory. - -You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) - -personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup -a configuration file that i use in all my projects .php-cs-fixer.php: - -```php -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - ]) - ); -``` - -#### PHP Codesniffer - -The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra -rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. - -it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. - -Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have -guessed we are adding some extra rules. - -Lets install the ruleset with composer -```shell -composer require --dev mnapoli/hard-mode -``` - -and add a configuration file to actually use it '.phpcs.xml.dist' -```xml - - - - - src - - - -``` - -running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. - -``` -[vagrant@archlinux app]$ ./vendor/bin/phpcs - -FILE: src/Bootstrap.php ----------------------------------------------------------------------------------------------------- -FOUND 4 ERRORS AFFECTING 4 LINES ----------------------------------------------------------------------------------------------------- - 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. - 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead ----------------------------------------------------------------------------------------------------- -PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY ----------------------------------------------------------------------------------------------------- - -Time: 639ms; Memory: 10MB -``` - -You can then use `./vendor/bin/phpcbf` to try to fix them - - -#### Symfony Var-Dumper - -another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small -functions. - -dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects -or arrays. - -dd() on the other hand is a function that dumps its parameters and then exits the php-script. - -you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. - -#### Composer scripts - -now we have a few commands that are available on the command line. i personally do not like to type complex commands -with lots of parameters by hand all the time, so i added a few lines to my composer.json: - -```json -"scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" -}, -``` - -that way i can just type "composer" followed by the command name in the root of my project. if i want to start the -php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the -time. - -You could also configure PhpStorm to automatically run these commands in the background and highlight the violations -directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my -flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. - -My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before -commiting and pushing the code. - -[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/app/data/pages/05-http.md b/app/data/pages/05-http.md deleted file mode 100644 index 6166214..0000000 --- a/app/data/pages/05-http.md +++ /dev/null @@ -1,124 +0,0 @@ -[<< previous](04-development-helpers.md) | [next >>](06-router.md) - -### HTTP - -PHP already has a few things built in to make working with HTTP easier. For example there are the -[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. - -These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, -if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, -then you will want a class with a nice object-oriented interface that you can use in your application instead. - -Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The -standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php -projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. - -As this is a widely adopted standard there are already several implementations available for us to use. I will choose -the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. - -Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a -[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. - -Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use -that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding -the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). - - -to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit -enter - -Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): - -```php -$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); -$response = new \Laminas\Diactoros\Response; -$response->getBody()->write('Hello World! '); -$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); -``` - -This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. - -In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the -write()-Method on that object. - - -To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: - -```php -echo $response->getBody(); -``` - -This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only -stores data. - -You can play around with the other methods of the Request object and take a look at its content with the dd() function. - -```php -dd($response) -``` - -Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot -be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which -creates a copy of the request object with the changed property and returns that clone: - -```php -$response = $response->withStatus(200); -$response = $response->withAddedHeader('Content-type', 'application/json'); -``` - -If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been -defined this way. - -But if you have been keeping attention you might argue that the following line should not work if the request object is -immutable. - -```php -$response->getBody()->write('Hello World!'); -``` - -The response-body implements a stream interface which is immutable for some reasons that are described in the -[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be -aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in -the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. -I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content -to a response object. - -```php -$body = $response->getBody(); -$body->write('Hello World!'); -$response = $response->withBody($body); -``` - -Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our -output-logic a little bit more. Replace the line that echos the response-body with the following: - -```php -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); -``` - -This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a -webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. - -Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. - -Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter - - -[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/app/data/pages/06-router.md b/app/data/pages/06-router.md deleted file mode 100644 index 6c39ae5..0000000 --- a/app/data/pages/06-router.md +++ /dev/null @@ -1,101 +0,0 @@ -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) - -### Router - -A router dispatches to different handlers depending on rules that you have set up. - -With your current setup it does not matter what URL is used to access the application, it will always result in the same -response. So let's fix that now. - -I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own -favorite package. - -Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) - -By now you know how to install Composer packages, so I will leave that to you. - -Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. - -```php -$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -switch ($routeInfo[0]) { - case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: - $response = (new \Laminas\Diactoros\Response)->withStatus(405); - $response->getBody()->write('Method not allowed'); - $response = $response->withStatus(405); - break; - case \FastRoute\Dispatcher::FOUND: - $handler = $routeInfo[1]; - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - /** @var \Psr\Http\Message\ResponseInterface $response */ - $response = call_user_func($handler, $request); - break; - case \FastRoute\Dispatcher::NOT_FOUND: - default: - $response = (new \Laminas\Diactoros\Response)->withStatus(404); - $response->getBody()->write('Not Found!'); - break; -} -``` - -In the first part of the code, you are registering the available routes for your application. In the second part, the -dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, -we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. -If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. - -This setup might work for really small applications, but once you start adding a few routes your bootstrap file will -quickly get cluttered. So let's move them out into a separate file. - -Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; - -```php -addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}; -``` - -Now let's rewrite the route dispatcher part to use the `Routes.php` file. - -```php -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); -``` - -This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. - -Of course we now need to add the 'config' folder to the configuration files of our -devhelpers so that they can scan that directory as well. - -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/app/data/pages/07-dispatching-to-a-class.md b/app/data/pages/07-dispatching-to-a-class.md deleted file mode 100644 index 0c961a4..0000000 --- a/app/data/pages/07-dispatching-to-a-class.md +++ /dev/null @@ -1,137 +0,0 @@ -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) - -### Dispatching to a Class - -In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). -MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to -learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) -and the followup posts. - -So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). - -We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other -common names are 'Controllers' or 'Actions'. - -Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. -In there, create a `Hello.php` file. - -```php -getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - } -} -``` - -You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) -that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is -fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement -it in some other parts of our application as well. In order to use that Interface we have to require it with composer: -'composer require psr/http-server-handler'. - -The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. -At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. - -Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: - -```php -return function(\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); - $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); -}; -``` - -Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared -the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing -the response to the one we defined for the second route. - -To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: - -```php -case \FastRoute\Dispatcher::FOUND: - $handler = new $routeInfo[1]; - if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { - throw new \Exception('Invalid Requesthandler'); - } - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - assert($response instanceof \Psr\Http\Message\ResponseInterface) - break; -``` - -So instead of just calling a method you are now instantiating an object and then calling the method on it. - -Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. - -And of course don't forget to commit your changes. - -Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still -generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for -example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still -have our whoopsie error-handler but i like to be more explicit in my control flow. - -In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new -Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and -InternalServerError. All three should extend phps Base Exception class. - -Here is my NotFound.php for example. - -```php - $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - break; - case Dispatcher::METHOD_NOT_ALLOWED: - throw new MethodNotAllowed; - - case Dispatcher::NOT_FOUND: - default: - throw new NotFound; - } -} catch (MethodNotAllowed) { - $response = (new Response)->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = (new Response)->withStatus(404); - $response->getBody()->write('Not Found'); -} catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); -} -``` - -Check if our code still works, try to trigger some errors, run phpstan and the fix command -and don't forget to commit your changes. - -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/app/data/pages/08-inversion-of-control.md b/app/data/pages/08-inversion-of-control.md deleted file mode 100644 index 21f4f23..0000000 --- a/app/data/pages/08-inversion-of-control.md +++ /dev/null @@ -1,54 +0,0 @@ -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) - -### Inversion of Control - -In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we -want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then -we would need to edit every one of our request handlers to call a different constructor of the class. - -The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that -instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done -with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). - -If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is -implemented, it will make sense. - -Change your `Hello` action to the following: - -```php -getAttribute('name', 'Stranger'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: - -```php -$handler = new $className($response); -``` - -Of course we need to also update all the other handlers. - -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/app/data/pages/09-dependency-injector.md b/app/data/pages/09-dependency-injector.md deleted file mode 100644 index 7f7c6a2..0000000 --- a/app/data/pages/09-dependency-injector.md +++ /dev/null @@ -1,213 +0,0 @@ -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) - -### Dependency Injector - -A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when -the class is instantiated. - -Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work -with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look -for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). - -I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) -out of the box. - -After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: - -```php -addDefinitions([ - \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), - \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), -]); - -return $builder->build(); -``` - -In this file we create a containerbuilder, add some definitions to it and return the container. -As the container supports autowiring we only need to define services where we want to use a specific implementation of -an interface. - -In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to -tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second -exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. - -Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), -as we will use that extensively. - -Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally -to create our Handler-Object - -```php -$container = require __DIR__ . '/../config/dependencies.php'; -assert($container instanceof \Psr\Container\ContainerInterface); - -$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); -assert($request instanceof \Psr\Http\Message\ServerRequestInterface); -``` - -The other part that has to be changed is the dispatching of the route. Before you had the following code: - -```php -$className = $routeInfo[1]; -$handler = new $className($response); -assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Change that to the following: - -```php -/** @var RequestHandlerInterface $handler */ -$className = $routeInfo[1]; -$handler = $container->get($className); -assert($handler instanceof RequestHandlerInterface); -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Make sure to use the container fetch the response object in the catch blocks as well: - -```php -} catch (MethodNotAllowed) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(404); - $response->getBody()->write('Not Found'); -} -``` - -Now all your controller constructor dependencies will be automatically resolved with PHP-DI. - -We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons -in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call -`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we -want to work with a different date in a test. - -Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation -that creates us a DateTimeImmutable object with the current date and time. - -src/Service/Time/Now.php: -```php -namespace Lubian\NoFramework\Service\Time; - -interface Now -{ - public function __invoke(): \DateTimeImmutable; -} -``` -src/Service/Time/SystemClockNow.php: -```php -namespace Lubian\NoFramework\Service\Time; - -final class SystemClockNow implements Now -{ - - public function __invoke(): \DateTimeImmutable - { - return new \DateTimeImmutable; - } -} -``` -If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and -update the handle-method to use the new class property: - -```php -getAttribute('name', 'Stranger'); - $nowAsString = ($this->now)()->format('H:i:s'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowAsString); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI -automatically figures out what classes are requested in the constructor and tries to create the objects needed. - -But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred -S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: - -```php - public function __construct( - private ResponseInterface $response, - private Now $now, - ) -``` - -When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation -should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: - -```php -\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), -``` - -we could also use the PHP-DI create method to delegate the object creation to the container implementation: -```php -\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), -``` - -this way the container can try to resolve any dependencies that the class might have internally, but prefer the other -method because we are not depending on this specific dependency injection implementation. - -Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are -requesting the Hello action. - -If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As -we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way -we wont get any warnings for this particular issue, but for any other issues we add to our code. - -Update the phpstan.neon file to include a "baseline" file: - -``` -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` - -if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and -ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just -'baseline' - -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/app/data/pages/10-invoker.md b/app/data/pages/10-invoker.md deleted file mode 100644 index 3033fae..0000000 --- a/app/data/pages/10-invoker.md +++ /dev/null @@ -1,102 +0,0 @@ -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) - -### Invoker - -Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the -one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long -with some services and not the whole Requestobject with all its various properties. - -If we take our Hello action for example we only need a response object, the time service and the 'name' information from -the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named -the class hello and it would be redundant to also call the the method hello. So an updated version of that class could -look like this: - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - string $name = 'Stranger', - ): ResponseInterface - { - $body = $this->response->getBody(); - $nowString = $now->get()->format('H:i:s'); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowString); - return $response - ->withBody($body) - ->withStatus(200); - } -} -``` - -It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short -closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the -rootpath of our application yet. - -```php -$r->addRoute('GET', '/hello[/{name}]', Hello::class); -$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); -$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -``` - -In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the -route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that -class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what -arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router -if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple -callable we would need to manually use reflection as well to resolve all the arguments. - -But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) -which handles all of that for us. - -After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo -switch section in the Bootstrap.php file to use the container->call() method: - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2]; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$response = $container->call($handler, $args); -``` - -Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. - -But by now you should know that I do not like to depend on specific implementations and the call method is not defined in -the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container -or any other implementation. - -Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific -container implementation so we could use it with any container that implements the ContainerInterface. And best of all -the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that -we could implement if we ever want to write our own implementation or we could write an adapter that uses a different -class that solves the same problem. - -But for now we are using the solution provided by PHP-DI. -So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2] ?? []; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$invoker = $container->get(InvokerInterface::class); -assert($invoker instanceof InvokerInterface); -$response = $invoker->call($handler, $args); -assert($response instanceof ResponseInterface); -``` - -Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) -by php, and even some more. - -But let us move on to something more fun and add some templating functionality to our application as we are trying to build -a website in the end. - -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/app/data/pages/11-templating.md b/app/data/pages/11-templating.md deleted file mode 100644 index 7bfe1aa..0000000 --- a/app/data/pages/11-templating.md +++ /dev/null @@ -1,236 +0,0 @@ -[<< previous](10-invoker.md) | [next >>](12-configuration.md) - -### Templating - -A template engine is not necessary with PHP because the language itself can take care of that. But it can make things -like escaping values easier. They also make it easier to draw a clear line between your application logic and the -template files which should only put your variables into the HTML code. - -A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please -also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. -Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. - -For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install -that package before you continue (`composer require mustache/mustache`). - -Another well known alternative would be [Twig](http://twig.sensiolabs.org/). - -Now please go and have a look at the source code of the -[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class -does not implement an interface. - -You could just type hint against the concrete class. But the problem with this approach is that you create tight -coupling. - -In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the -implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want -to add functionality to the engine. You can't do that without going back and changing all your code that is tightly -coupled. - -What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need -another implementation, you just implement that interface in your new class and inject the new class instead. - -Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). -This sounds a lot more complicated than it is, so just follow along. - -First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). -This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. -A class can implement multiple interfaces if necessary. - -So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a -new folder in your `src/` folder with the name `Template` where you can put all the template related things. - -In there create a new interface `Renderer.php` that looks like this: - -```php - $data */ - public function render(string $template, array $data = []) : string; -} -``` - -Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file -`MustacheRenderer.php` with the following content: - -```php -engine->render($template, $data); - } -} -``` - -As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple -and only fulfills the interface. - -Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know -which implementation he has to inject when you hint for the interface. Add this line: - -```php -[ - ... - \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) - ->constructor(new Mustache_Engine), -] -``` - -Now update the Hello.php class to require an implementation of our renderer interface -and use that to render a string using mustache syntax. - - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - Renderer $renderer, - string $name = 'Stranger', - ): ResponseInterface { - $body = $response->getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render( - 'Hello {{name}}, the time is {{now}}!', - $data, - ); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} -``` - -Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. -But what we want is template files, so let's go back and change that. - -To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the -`dependencies.php` file and add the following code: - -```php -[ - ... - Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), - Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), -] -``` - -We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. -Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have -to rename all the template files. - -To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the -dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader -in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object -we can then use it in the factory. - -In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: - -``` -

Hello World

-Hello {{ name }} -``` - -Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` - -Navigate to the hello page in your browser to make sure everything works. - -One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies -file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration -values. - -Lets create a 'Settings' class in our './src' Folder: - -```php -addDefinitions([ - Settings::class => fn () => require __DIR__ '/settings.php', - ResponseInterface::class => create(Response::class), - ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), - Renderer::class => fn (ME $me) => new Mustache($me), - MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), -]); - -return $builder->build(); -``` - - - -And as always, don't forget to commit your changes. - - -[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/app/data/pages/12-configuration.md b/app/data/pages/12-configuration.md deleted file mode 100644 index 4b60c19..0000000 --- a/app/data/pages/12-configuration.md +++ /dev/null @@ -1,200 +0,0 @@ -[<< previous](11-templating.md) | [next >>](13-refactoring.md) - -### Configuration - -In the last chapter we added some more definitions to our dependencies.php in that definitions -we needed to pass quite a few configuration settings and filesystem strings to the constructors -of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. - -As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. - -As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. - -Lets create a 'Settings' class in our './src' Folder: - -```php -filePath; - } -} -``` - -If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files -and craft a settings object from them. - -As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another -factory for our Container because everyone should have a Friend :) - -```php -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn () => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} -``` - -For this to work we need to change our dependencies.php file to just return the array of definitions: -And here we can instantly use the Settings object to create our template engine. - -```php - fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), -]; -``` - -Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: - -```php -... -error_reporting(E_ALL); - -$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); -$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); -... -``` - -Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. - -[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/app/data/pages/13-refactoring.md b/app/data/pages/13-refactoring.md deleted file mode 100644 index 6dbbb8d..0000000 --- a/app/data/pages/13-refactoring.md +++ /dev/null @@ -1,373 +0,0 @@ -[<< previous](12-configuration.md) | [next >>](14-middleware.md) - -### Refactoring - -By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no -reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. -After all the bootstrap file should just set up the classes needed for the handling logic and execute them. - -At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. -As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the -method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non -static and extracted an interface. - -'./src/Http/Emitter.php' -```php -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - echo $response->getBody(); - } -} -``` -After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following -code in the Bootstrap.php to emit the response: - -```php -/** @var Emitter $emitter */ -$emitter = $container->get(Emitter::class); -$emitter->emit($response); -``` - -If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily -write an adapter that implements your emitter interface and wraps that more advanced emitter - -Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and -calling the routerhandler that in the passes the request to a function and gets the response. - -For this to steps to be seperated we are going to create two more classes: -1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object -2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the - requestobject, fetches the correct object from the container and calls it to create a response. - -Lets create the HandlerInterface first: - -```php -getAttribute($this->routeAttributeName, false); - assert($handler !== false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} - -``` - -We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. -The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler -as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in -the next chapter. - -```php -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} -``` - -Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. -```php -[ - '...', - Emitter::class => fn (BasicEmitter $e) => $e, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, -], -``` - -And then we can update our Bootstrap.php to fetch all the services and let them handle the request. - -```php -... -$routeMiddleWare = $container->get(MiddlewareInterface::class); -assert($routeMiddleWare instanceof MiddlewareInterface); -$handler = $container->get(RoutedRequestHandler::class); -assert($handler instanceof RequestHandlerInterface); -$emitter = $container->get(Emitter::class); -assert($emitter instanceof Emitter); - -$request = $container->get(ServerRequestInterface::class); -assert($request instanceof ServerRequestInterface); - -$response = $routeMiddleWare->process($request, $handler); -$emitter->emit($response); -``` -Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of -code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). - -So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. - -I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class -should require to function properly. - -* A RequestFactory - We want our Kernel to be able to build the request itself -* An Emitter - Without an Emitter we will not be able to send the response to the client -* RouteMiddleware - To decore the request with the correct handler for the requested route -* RequestHandler - To delegate the request to the correct funtion that creates the response - -As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface -to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our -interface: - -```php -factory::fromGlobals(); - } - - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} -``` - -For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: - -```php -routeMiddleware->process($request, $this->handler); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} - -``` - -We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines - -```php -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); -``` - -You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking -at the Whoops output and adding the needed definitions to the dependencies.php file. - -And as always, don't forget to commit your changes. - -[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/app/data/pages/14-middleware.md b/app/data/pages/14-middleware.md deleted file mode 100644 index 81f82a5..0000000 --- a/app/data/pages/14-middleware.md +++ /dev/null @@ -1,303 +0,0 @@ -[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) - -### Middleware - -In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain -a bit more about what this interface does and why it is used in many applications. - -The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets -passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all -the middlewars to the client/emitter. - -So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the -response after it gets created by our handlers. - -So lets take a look at the middleware and the requesthandler interfaces - -```php -interface MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; -} - -interface RequestHandlerInterface -{ - public function handle(ServerRequestInterface $request): ResponseInterface; -} -``` - -The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a -requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the -response. - -But the middleware could just ignore the handler and produce a response on its own as the interface just requires us -to produce a response. - -A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users -that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the -database. - -In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates -the request with an 'isAuthenticated' attribute. - -If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that -response is not already cached, than we let the handler create the response and store that in the cache for a few -seconds - -```php -interface CacheInterface -{ - public function get(string $key, callable $resolver, int $ttl): mixed; -} -``` - -The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one -defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses -the callable to produce the value and stores it for the time specified in ttl. - -so lets write our caching middleware: - -```php -final class CachingMiddleware implements MiddlewareInterface -{ - public function __construct(private CacheInterface $cache){} - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { - $key = $request->getUri()->getPath(); - return $this->cache->get($key, fn() => $handler->handle($request), 10); - } - return $handler->handle($request); - } -} -``` - -we can also modify the response after it has been created by our application, for example we could implement a gzip -middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser -know that our application is used to serve dank memes: - -```php -final class DankMemeMiddleware implements MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - $response = $handler->handle($request); - return $response->withAddedHeader('Meme', 'Dank'); - } -} -``` - -but for our application we are going to just add two external middlewares: - -* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. -* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware - -```bash -composer require middlewares/trailing-slash -composer require middlewares/whoops -``` - -The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the -application as well as the middleware stack. - -Our desired request -> response flow looks something like this: - - Client - | ^ - v | - Kernel - | ^ - v | - Whoops Middleware - | ^ - v | - TrailingSlash - | ^ - v | - Routing - | ^ - v | - ContainerResolver - | ^ - v | - Controller/Action - -As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every -middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. - -```php -interface Pipeline -{ - public function dispatch(ServerRequestInterface $request): ResponseInterface; -} -``` - -And our implementation looks something like this: - -```php - $middlewares - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} -``` - -Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code -that should produce our response. - -In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument -and store that itself as the current tip. - -There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) - -Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline -Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline - -```php -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} -``` - -And configure the container to use the Factory to create the Pipeline: - -```php - ..., - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - ... -``` -And of course a new file called middlewares.php in our config folder: -```php -pipeline->dispatch($request); -} -``` - -Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our -pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a -dependency container, if you want you can do that as well. - -**A quick note about docblocks:** You might have noticed, that I rarely add docblocks to my the code in the examples, and -when I do it seems kind of random. My philosophy is that I only add docblocks when there is no way to automatically get -the exact type from the code itself. For me docblocks only serve two purposes: help my IDE to understand what it choices -it has for code completion and to help the static analysis to better understand the code. There is a great blogpost -about the [cost and value of DocBlocks](https://localheinz.com/blog/2018/05/06/cost-and-value-of-docblocks/), although it -is written in 2018 at a time before PHP 7.4 was around everything written there is still valid today. - -[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) diff --git a/app/data/pages/15-adding-content.md b/app/data/pages/15-adding-content.md deleted file mode 100644 index 64562fa..0000000 --- a/app/data/pages/15-adding-content.md +++ /dev/null @@ -1,253 +0,0 @@ -[<< previous](14-middleware.md) | [next >>](16-data-repository.md) - -### Adding Content - -By now we did not really display anything but some examples to in our application and it is now time to make our app -display some content. For example we could our app be able to display the Markdown files used in this tutorial as -nicely rendered HTML Pages that can be viewed in the browser instead of the editor you are using. - -So lets start by copying the markdown files to our app directory. I have created a new folder 'data/pages' and placed all -the markdown files in there. - -Next we need a markdown parser, a pretty simple one is [Parsedown](https://parsedown.org/), if you want more features -you could also use the [Commonmark parser](https://commonmark.thephpleague.com/). I will choose Parsedown here, but you -can use whatever you like. - -After installing Parsedown lets write a Markdownparser interface and an implementation using parsedown. - -We only need one function that receives a string of Markdown and returns the HTML represantion (as a string as well). - - - -```php -parser->parse($markdown); - } -} -``` - -We could now use the ParsedownRender class directly in our actions by typehinting the classname as an argument to the -constructor or a method, but as we always want to rely on an interface instead of an implementation we need to define -the the ParsedownRenderer as the correct implementation for the MarkdownRenderer interface in the dependencies file: - -```php -... - \Lubian\NoFramework\Template\MarkdownParser::class => fn(\Lubian\NoFramework\Template\ParsedownParser $p) => $p, -... -``` - -You can test that in our "Other.php" action and try out if the Parser works and is able to render Markdown to HTML: - -```php -public function someFunctionName(ResponseInterface $response, MarkdownParser $parser): ResponseInterface -{ - $html = $parser->parse('This *works* **too!**'); - $response->getBody()->write($html); - return $response->withStatus(200); -} -``` - -But we want to display complete Pages written in Markdown, it would also be neat to be able to display a list of all -available pages. For that we need a few things: - -Firstly we need two new Templates, one for the list of the Pages, and the second one for displaying a single pages -content. Create a new folder in `templates/page` with to files: - -`templates/page/list.html` -```html - - - - - Pages - - - -
- -
- - -``` - -This template iterates over a provided array of pages, each element consists of the two properties: an id and a title, -those are simply displayed using an unordered list. - -`templates/page/show.html` -```html - - - - - {{title}} - - - - - - -
- {{{content}}} -
- - -``` - -The second templates displays a single rendered markdown page. As data it expects the title and the content as array. -I used an extra bracket for the content ```{{{content}}}``` so that the Mustache-Renderer does not escape the provided -html and thereby destroys the the parsed markdown. - -You might have spotted that I added [Pico.css](https://picocss.com/) which is just a very small css framework to make the -pages a little bit nicer to look at. It mostly provides some typography styles that work great with rendered Markdown, -but you can leave that out or use any other css framework you like. There is also some Javascript that adds syntax -highlighting to the code. - -After you have taken care of the templating side we can now create an new Action class with two methods to display use -our markdown files and the templates to create the pages. As we have two templates I propose to use Two methods in our -Action: -`src/Action/Page.php` -```php -function show(string $name): \Psr\Http\Message\ResponseInterface; -function list(): \Psr\Http\Message\ResponseInterface; -``` - -Lets define two routes. `/page` should display the overview of all pages, and if the add the name of chapter to the -route, `/page/adding-content` for example, the show action should be called with the name as a variable: - -`config/routes.php` -```php -$r->addRoute('GET', '/page', [Page::class, 'list']); -$r->addRoute('GET', '/page/{page}', [Page::class, 'show']); -``` - -Here is my Implementation. If have added a little regex replacement in the show method that replaces the links to the -next and previous chapter so that it works with our routing configuration. - -`src/Action/Page.php` -```php -getPages(), - fn (string $filename) => str_contains($filename, $page) - ) - )[0]; - $markdown = file_get_contents($page); - - // fix the next and previous buttons to work with our routing - $markdown = preg_replace('/\(\d\d-/m', '(', $markdown); - $markdown = str_replace('.md)', ')', $markdown); - - $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); - $data = [ - 'title' => substr($page, 3), - 'content' => $this->parser->parse($markdown), - ]; - $html = $this->renderer->render('page/show', $data); - $this->response->getBody()->write($html); - return $this->response; - } - - public function list(): ResponseInterface - { - $pages = array_map(function (string $page) { - $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); - return [ - 'id' => substr($page, 0, 2), - 'title' => substr($page, 3), - ]; - }, $this->getPages()); - $html = $this->renderer->render('page/list', ['pages' => $pages]); - $this->response->getBody()->write($html); - return $this->response; - } - - /** - * @return string[] - */ - private function getPages(): array - { - $files = glob($this->pagesPath . '*.md'); - if ($files === false) { - throw new InternalServerError('cannot read pages'); - } - return $files; - } -} -``` - -You can now navigate your Browser to [localhost:1234/page][http://localhost:1234/page] and try out if everything works. - -Of course this code is far from looking good. We heavily rely on the pages being files in the filesystem, and the action -should never be aware of the filesystem in the first place, also we have a lot of string replacements and other repetetive -code in the file. And phpstan is gonna scream at us a lot, but if we rewrite the code to satisfy all the checks we would -add even more lines to that simple class, so lets move on to the next chapter where we move all the logic to seperate -classes following our holy SOLID principles :) - - -[<< previous](14-middleware.md) | [next >>](16-data-repository.md) diff --git a/app/data/pages/16-data-repository.md b/app/data/pages/16-data-repository.md deleted file mode 100644 index d9a3218..0000000 --- a/app/data/pages/16-data-repository.md +++ /dev/null @@ -1,265 +0,0 @@ -[<< previous](15-adding-content.md) | [next >>](17-performance.md) - -## Data Repository - -At the end of the last chapter I mentioned being unhappy with our Pages action, because there is to much stuff happening -there. We are firstly receiving some Arguments, then we are using those to query the filesytem for the given page, -loading the specific file from the filesystem, rendering the markdown, passing the markdown to the template renderer, -adding the resulting html to the response and then returning the response. - -In order to make our pageaction independent from the filesystem and move the code that is responsible for reading the -files -to a better place I want to introduce -the [Repository Pattern](https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html). - -I want to start by creating a class that represents the Data that is included in a page so that. For now I can spot -three -distrinct attributes. - -* the ID (or chapternumber) -* the title (or name) -* the content - -Currently all those properties are always available, but we might later be able to create new pages and store them, but -at that point in time we are not yet aware of the new available ID, so we should leave that property nullable. This -allows -us to create an object without an id and let the code that actually saves the object to a persistant store define a -valid -id on saving. - -Lets create an new Namespace called `Model` and put a `MarkdownPage.php` class in there: - -```php -dataPath . '*.md'); - if ($files === false) { - throw new InternalServerError('cannot read pages'); - } - return array_map(function (string $filename) { - $content = file_get_contents($filename); - if ($content === false) { - throw new InternalServerError('cannot read pages'); - } - $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); - return new MarkdownPage( - (int) substr($idAndTitle, 0, 2), - substr($idAndTitle, 3), - $content - ); - }, $files); - } - - public function byName(string $name): MarkdownPage - { - $pages = array_values( - array_filter( - $this->all(), - fn (MarkdownPage $p) => $p->title === $name, - ) - ); - - if (count($pages) !== 1) { - throw new NotFound; - } - - return $pages[0]; - } -} -``` - -With that in place we need to add the required `$pagesPath` to our settings class and add specify that in our -configuration. - -`src/Settings.php` - -```php -final class Settings -{ - public function __construct( - public readonly string $environment, - public readonly string $dependenciesFile, - public readonly string $middlewaresFile, - public readonly string $templateDir, - public readonly string $templateExtension, - public readonly string $pagesPath, - ) { - } -} -``` - -`config/settings.php` - -```php -return new Settings( - environment: 'prod', - dependenciesFile: __DIR__ . '/dependencies.php', - middlewaresFile: __DIR__ . '/middlewares.php', - templateDir: __DIR__ . '/../templates', - templateExtension: '.html', - pagesPath: __DIR__ . '/../data/pages/', -); -``` - -Of course we need to define the correct implementation for the container to choose when we are requesting the Repository -interface: -`conf/dependencies.php` - -```php -MarkdownPageRepo::class => fn (FileSystemMarkdownPageRepo $r) => $r, -FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), -``` - -Now you can request the MarkdownPageRepo Interface in your page action and use the defined functions to get the -MarkdownPage -Objects. My `src/Action/Page.php` looks like this now: - -```php -repo->byName($page); - - // fix the next and previous buttons to work with our routing - $content = preg_replace('/\(\d\d-/m', '(', $page->content); - assert(is_string($content)); - $content = str_replace('.md)', ')', $content); - - $data = [ - 'title' => $page->title, - 'content' => $this->parser->parse($content), - ]; - - $html = $this->renderer->render('page/show', $data); - $this->response->getBody()->write($html); - return $this->response; - } - - public function list(): ResponseInterface - { - $pages = array_map(function (MarkdownPage $page) { - return [ - 'id' => $page->id, - 'title' => $page->content, - ]; - }, $this->repo->all()); - - $html = $this->renderer->render('page/list', ['pages' => $pages]); - $this->response->getBody()->write($html); - return $this->response; - } -} -``` - -Check the page in your browser if everything still works, don't forget to run phpstan and the others fixers before -committing your changes and moving on to the next chapter. - -[<< previous](15-adding-content.md) | [next >>](17-performance.md) diff --git a/app/data/pages/17-performance.md b/app/data/pages/17-performance.md deleted file mode 100644 index c83c7d5..0000000 --- a/app/data/pages/17-performance.md +++ /dev/null @@ -1,43 +0,0 @@ -[<< previous](16-data-repository.md) | [next >>](18-caching.md) - -## Autoloading performance - -Although our application is still very small and you should not really experience any performance issues right now, -there are still some things we can already consider and take a look at. If I check the network tab in my browser it takes -about 90-400ms to show a simple rendered markdownpage, with is sort of ok but in my opinion way to long as we are not -really doing anything and do not connect to any external services. Mostly we are just reading around 16 markdown files, -a template, some config files here and there and parse some markdown. So that should not really take that long. - -The problem is, that we heavily rely on autoloading for all our class files, in the `src` folder. And there are also -quite a lot of other files in composers `vendor` directory. To understand while this is becomming we should make -ourselves familiar with how [autoloading in php](https://www.php.net/manual/en/language.oop5.autoload.php) works. - -The basic idea is, that every class that php encounters has to be loaded from somewhere in the filesystem, we could -just require the files manually but that is tedious, unflexible and can often cause errors. - -The problem we are now facing is that the composer autoloader has some rules to determine from where in the filesystem -a class definition might be placed, then the autoloader tries to locate a file by the namespace and classname and if it -exists includes that file. - -If we only have a handfull of classes that does not take a lot of time, but as we are growing with our application this -easily takes longer than necesery, but fortunately composer has some options to speed up the class loading. - -Take a few minutes to read the documentation about [composer autoloader optimization](https://getcomposer.org/doc/articles/autoloader-optimization.md) - -You can try all 3 levels of optimizations, but we are going to stick with the first one for now, so lets create an -optimized classmap. - -`composer dump-autoload -o` - -After composer has finished you can start the devserver again with `composer serve` and take a look at the network tab -in your browsers devtools. - -In my case the response time falls down to under an average of 30ms with some spikes in between, but all in all it looks really good. -You can also try out the different optimization levels and see if you can spot any differences. - -Although the composer manual states not to use the optimtization in a dev environment I personally have not encountered -any errors with the first level of optimizations, so we can use that level here. If you add the line from the documentation -to your `composer.json` so that the autoloader gets optimized everytime we install new packages. - - -[<< previous](16-data-repository.md) | [next >>](18-caching.md) diff --git a/app/data/pages/18-caching.md b/app/data/pages/18-caching.md deleted file mode 100644 index 42e9cb1..0000000 --- a/app/data/pages/18-caching.md +++ /dev/null @@ -1,252 +0,0 @@ -[<< previous](17-performance.md) | [next >>](19-database.md) - -**DISClAIMER** I do not really have a lot of experience when it comes to caching, so this chapter is mostly some random -thoughts and ideas I wanted to explore when writing this tutorial, you should definitely take everything that is being -said here with caution and try to read up on some other sources. But that holds true for the whole tutorial anyway :) - -## Caching - -In the last chapter we greatly improved the perfomance for the lookup of all our classfiles, but currently we do not -have any real bottlenecks in our application like complex queries. - -But in a real application we are going to execute some really heavy and time intensive database queries that can take -quite a while to be completed. - -We can simulate that by adding a simple delay in our `FileSystemMarkdownPageRepo`. - -```php - return array_map(function (string $filename) { - usleep(rand(100, 400) * 1000); - $content = file_get_contents($filename); - if ($content === false) { - throw new InternalServerError('cannot read pages'); - } - $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); - return new MarkdownPage( - (int) substr($idAndTitle, 0, 2), - substr($idAndTitle, 3), - $content - ); -}); -``` - -Here I added a function that pauses the scripts execution for a random time between 100 and 400ms for every markdownpage -in every call of the `all()` method. - -If you open any page or even the listAction in you browser you will see, that it takes quite a time to render that page. -Although this is a silly example we do not really need to query the database on every request, so lets add a way to cache -the database results between requests. - -The PHP-Community has already adressed the issue of having easy to use access to cache libraries, there is the -[PSR-6 Caching Interface](https://www.php-fig.org/psr/psr-6) which gives us easy access to many different implementations, -then there is also a much simpler [PSR-16 Simple Cache](https://www.php-fig.org/psr/psr-16) which makes the use even more -easy, and most Caching Libraries implement Both interfaces anyway. You would think that this is more than enough solutions -to satisfy all the Caching needs around, but the Symfony People decided that Caching should be even simpler and easier -to use and defined their own [Interface](https://symfony.com/doc/current/components/cache.html#cache-component-contracts) -which only needs two methods. You should definitely take a look at the linked documentation as it really blew my mind -when I first encountered it. - -The basic idea is that you provide a callback that computes the requested value. The Cache implementation then checks -if it already has the value stored somewhere and if it doesnt it just executes the callback and stores the value for -future calls. - -It is really simple and great to use. In a real world application you should definitely use that or a PSR-16 implementation -but for this tutorial I wanted to roll out my own solution, so here we go. - -As always we are going to define an interface first, I am going to call it EasyCache and place it in the `Service/Cache` -namespace. I will require only one method which is base on the Symfony Cache Contract, and hast a key, a callback, and -the duration that the item should be cached as arguments. - -```php -cache->get( - $key, - fn () => $this->repo->all(), - 300 - ); - } - - public function byName(string $name): MarkdownPage - { - $key = base64_encode(self::class . 'byName' . $name); - return $this->cache->get( - $key, - fn () => $this->repo->byName($name), - 300 - ); - } -} -``` - -This simple wrapper just requires an EasyCache implementation and a MarkdownPageRepo in the constructor and uses them -to cache all queries for 5 minutes. The beauty is that we are not dependent on any implementation here, so we can switch -out the Repository or the Cache at any point down the road if we want to. - -In order to use that we need to update our `config/dependencies.php` to add an alias for the EasyCache interface as well -as defining our CachedMarkdownPageRepo as implementation for the MarkdownPageRepo interface: - -```php -MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, -EasyCache::class => fn (ApcuCache $c) => $c, -``` - -If we try to access our webpage now, we are getting an error, as PHP-DI has detected a circular dependency that cannot -be autowired. - -The Problem is that our CachedMarkdownPageRepo ist defined as the implementation for the MarkdownPageRepo, but it also -requires that exact interface as a dependency. To resolve this issue we need to manually tell the container how to build -the CachedMarkdownPageRepo by adding another line to the `config/dependencies.php` file: - -```php -CachedMarkdownPageRepo::class => fn (EasyCache $c, FileSystemMarkdownPageRepo $r) => new CachedMarkdownPageRepo($c, $r), -``` - -Here we explicitly require the FileSystemMarkdownPageRepo and us that to create the CachedMarkdownPageRepo object. - -When you now navigate to the pages list or to a specific page the first load should take a while (because of our added delay) -but the following request should be answered blazingly fast. - -Before moving on to the next chapter we can take the caching approach even further, in the middleware chapter I talked -about a simple CachingMiddleware that caches all the GET-Request for some seconds, as they should not change that often, -and we can bypass most of our application logic if we just complelety cache away the responses our application generates, -and return them quite early in our Middleware-Pipeline befor the router gets called, or the invoker calls the action, -which itself uses some other services to fetch all the needed data. - -We will introduce a new `Middleware` namespace to place our `Cache.php` middleware: -```php -getMethod() !== 'GET') { - return $handler->handle($request); - } - $keyHash = base64_encode($request->getUri()->getPath()); - $result = $this->cache->get( - $keyHash, - fn () => Serializer::toString($handler->handle($request)), - 300 - ); - return Serializer::fromString($result); - } -} -``` - -The code is quite straight forward, but you might be confused by the Responseserializer I have added here, we need this -because the response body is a stream object, which doesnt always gets serialized correctly, therefore I use a class from -the laminas project to to all the heavy lifting for us. - -We need to add the now middleware to the `config/middlewares.php` file. - -```php ->](19-database.md) diff --git a/app/phpstan-baseline.neon b/app/phpstan-baseline.neon deleted file mode 100644 index 61697a1..0000000 --- a/app/phpstan-baseline.neon +++ /dev/null @@ -1,7 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" - count: 1 - path: src/Http/InvokerRoutedHandler.php - diff --git a/app/phpstan.neon b/app/phpstan.neon deleted file mode 100644 index 2eac45a..0000000 --- a/app/phpstan.neon +++ /dev/null @@ -1,8 +0,0 @@ -includes: - - phpstan-baseline.neon - -parameters: - level: max - paths: - - src - - config \ No newline at end of file diff --git a/app/public/css/spectre-exp.min.css b/app/public/css/spectre-exp.min.css deleted file mode 100644 index d313774..0000000 --- a/app/public/css/spectre-exp.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/app/public/css/spectre-icons.min.css b/app/public/css/spectre-icons.min.css deleted file mode 100644 index 0276f7b..0000000 --- a/app/public/css/spectre-icons.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/app/public/css/spectre.min.css b/app/public/css/spectre.min.css deleted file mode 100644 index 0fe23d9..0000000 --- a/app/public/css/spectre.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/app/public/index.php b/app/public/index.php deleted file mode 100644 index 32f5eb3..0000000 --- a/app/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render('hello', $data); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/app/src/Action/Other.php b/app/src/Action/Other.php deleted file mode 100644 index da9ceaf..0000000 --- a/app/src/Action/Other.php +++ /dev/null @@ -1,16 +0,0 @@ -parse('This *works* **too!**'); - $response->getBody()->write($html); - return $response->withStatus(200); - } -} diff --git a/app/src/Action/Page.php b/app/src/Action/Page.php deleted file mode 100644 index 96696e4..0000000 --- a/app/src/Action/Page.php +++ /dev/null @@ -1,60 +0,0 @@ -repo->byName($page); - - // fix the next and previous buttons to work with our routing - $content = preg_replace('/\(\d\d-/m', '(', $page->content); - assert(is_string($content)); - $content = str_replace('.md)', ')', $content); - - $data = [ - 'title' => $page->title, - 'content' => $this->parser->parse($content), - ]; - - $html = $this->renderer->render('page/show', $data); - $this->response->getBody()->write($html); - return $this->response; - } - - public function list(): ResponseInterface - { - $pages = array_map(function (MarkdownPage $page) { - return [ - 'id' => $page->id, - 'title' => $page->title, - ]; - }, $this->repo->all()); - - $html = $this->renderer->render('page/list', ['pages' => $pages]); - $this->response->getBody()->write($html); - return $this->response; - } -} diff --git a/app/src/Bootstrap.php b/app/src/Bootstrap.php deleted file mode 100644 index 3abc2e5..0000000 --- a/app/src/Bootstrap.php +++ /dev/null @@ -1,40 +0,0 @@ -getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); diff --git a/app/src/Exception/InternalServerError.php b/app/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/app/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ -factory::fromGlobals(); - } - - /** - * @param UriInterface|string $uri - * @param array $serverParams - */ - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} diff --git a/app/src/Factory/FileSystemSettingsProvider.php b/app/src/Factory/FileSystemSettingsProvider.php deleted file mode 100644 index f071078..0000000 --- a/app/src/Factory/FileSystemSettingsProvider.php +++ /dev/null @@ -1,22 +0,0 @@ -filePath; - assert($settings instanceof Settings); - return $settings; - } -} diff --git a/app/src/Factory/PipelineProvider.php b/app/src/Factory/PipelineProvider.php deleted file mode 100644 index 77738f8..0000000 --- a/app/src/Factory/PipelineProvider.php +++ /dev/null @@ -1,25 +0,0 @@ -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} diff --git a/app/src/Factory/RequestFactory.php b/app/src/Factory/RequestFactory.php deleted file mode 100644 index 2b17abc..0000000 --- a/app/src/Factory/RequestFactory.php +++ /dev/null @@ -1,11 +0,0 @@ -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = $settings; - $builder->addDefinitions($dependencies); - // $builder->enableCompilation('/tmp'); - return $builder->build(); - } -} diff --git a/app/src/Factory/SettingsProvider.php b/app/src/Factory/SettingsProvider.php deleted file mode 100644 index ce1c5f0..0000000 --- a/app/src/Factory/SettingsProvider.php +++ /dev/null @@ -1,10 +0,0 @@ -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - - echo $response->getBody(); - } -} diff --git a/app/src/Http/ContainerPipeline.php b/app/src/Http/ContainerPipeline.php deleted file mode 100644 index 816cedd..0000000 --- a/app/src/Http/ContainerPipeline.php +++ /dev/null @@ -1,82 +0,0 @@ - $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} diff --git a/app/src/Http/Emitter.php b/app/src/Http/Emitter.php deleted file mode 100644 index ce4c035..0000000 --- a/app/src/Http/Emitter.php +++ /dev/null @@ -1,10 +0,0 @@ -getAttribute($this->routeAttributeName, false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} diff --git a/app/src/Http/Pipeline.php b/app/src/Http/Pipeline.php deleted file mode 100644 index 1a9dcda..0000000 --- a/app/src/Http/Pipeline.php +++ /dev/null @@ -1,11 +0,0 @@ -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} diff --git a/app/src/Http/RoutedRequestHandler.php b/app/src/Http/RoutedRequestHandler.php deleted file mode 100644 index a7407c9..0000000 --- a/app/src/Http/RoutedRequestHandler.php +++ /dev/null @@ -1,10 +0,0 @@ -pipeline->dispatch($request); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} diff --git a/app/src/Middleware/Cache.php b/app/src/Middleware/Cache.php deleted file mode 100644 index 8460761..0000000 --- a/app/src/Middleware/Cache.php +++ /dev/null @@ -1,38 +0,0 @@ -getMethod() !== 'GET') { - return $handler->handle($request); - } - $keyHash = base64_encode($request->getUri()->getPath()); - $result = $this->cache->get( - $keyHash, - fn () => $this->serializer::toString($handler->handle($request)), - 300 - ); - assert(is_string($result)); - return $this->serializer::fromString($result); - } -} diff --git a/app/src/Model/MarkdownPage.php b/app/src/Model/MarkdownPage.php deleted file mode 100644 index df244fd..0000000 --- a/app/src/Model/MarkdownPage.php +++ /dev/null @@ -1,13 +0,0 @@ -cache->get( - $key, - fn () => $this->repo->all(), - 300 - ); - assert(is_array($result)); - foreach ($result as $page) { - assert($page instanceof MarkdownPage); - } - return $result; - } - - public function byName(string $name): MarkdownPage - { - $key = base64_encode(self::class . 'byName' . $name); - $result = $this->cache->get( - $key, - fn () => $this->repo->byName($name), - 300 - ); - assert($result instanceof MarkdownPage); - return $result; - } -} diff --git a/app/src/Repository/FileSystemMarkdownPageRepo.php b/app/src/Repository/FileSystemMarkdownPageRepo.php deleted file mode 100644 index cca350e..0000000 --- a/app/src/Repository/FileSystemMarkdownPageRepo.php +++ /dev/null @@ -1,61 +0,0 @@ -dataPath . '*.md'); - if ($files === false) { - throw new InternalServerError('cannot read pages'); - } - return array_map(function (string $filename) { - $content = file_get_contents($filename); - if ($content === false) { - throw new InternalServerError('cannot read pages'); - } - $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); - return new MarkdownPage( - (int) substr($idAndTitle, 0, 2), - substr($idAndTitle, 3), - $content - ); - }, $files); - } - - public function byName(string $name): MarkdownPage - { - $pages = array_values( - array_filter( - $this->all(), - fn (MarkdownPage $p) => $p->title === $name, - ) - ); - - if (count($pages) !== 1) { - throw new NotFound; - } - - return $pages[0]; - } -} diff --git a/app/src/Repository/MarkdownPageRepo.php b/app/src/Repository/MarkdownPageRepo.php deleted file mode 100644 index 0792d32..0000000 --- a/app/src/Repository/MarkdownPageRepo.php +++ /dev/null @@ -1,15 +0,0 @@ -engine->render($template, $data); - } -} diff --git a/app/src/Template/ParsedownParser.php b/app/src/Template/ParsedownParser.php deleted file mode 100644 index 2ffd287..0000000 --- a/app/src/Template/ParsedownParser.php +++ /dev/null @@ -1,17 +0,0 @@ -parser->parse($markdown); - } -} diff --git a/app/src/Template/Renderer.php b/app/src/Template/Renderer.php deleted file mode 100644 index ff916ed..0000000 --- a/app/src/Template/Renderer.php +++ /dev/null @@ -1,11 +0,0 @@ - $data - */ - public function render(string $template, array $data = []): string; -} diff --git a/app/templates/hello.html b/app/templates/hello.html deleted file mode 100644 index 15a4cd2..0000000 --- a/app/templates/hello.html +++ /dev/null @@ -1,6 +0,0 @@ -{{> partials/head }} -
-

Hello {{name}}

-

The time is {{now}}

-
-{{> partials/foot }} diff --git a/app/templates/page.html b/app/templates/page.html deleted file mode 100644 index c3c5284..0000000 --- a/app/templates/page.html +++ /dev/null @@ -1,5 +0,0 @@ -{{> partials/head }} -
- {{{content}}} -
-{{> partials/foot }} diff --git a/app/templates/page/list.html b/app/templates/page/list.html deleted file mode 100644 index bf42348..0000000 --- a/app/templates/page/list.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Pages - - - -
- -
- - \ No newline at end of file diff --git a/app/templates/page/show.html b/app/templates/page/show.html deleted file mode 100644 index abe295e..0000000 --- a/app/templates/page/show.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - {{title}} - - - - - - -
- {{{content}}} -
- - \ No newline at end of file diff --git a/app/templates/pagelist.html b/app/templates/pagelist.html deleted file mode 100644 index 538e2c4..0000000 --- a/app/templates/pagelist.html +++ /dev/null @@ -1,11 +0,0 @@ -{{> partials/head }} -
- -
-{{> partials/foot }} diff --git a/app/templates/partials/foot.html b/app/templates/partials/foot.html deleted file mode 100644 index 17c7245..0000000 --- a/app/templates/partials/foot.html +++ /dev/null @@ -1,3 +0,0 @@ -
- - \ No newline at end of file diff --git a/app/templates/partials/head.html b/app/templates/partials/head.html deleted file mode 100644 index 421d387..0000000 --- a/app/templates/partials/head.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - No Framework: {{title}} - - - - - -
diff --git a/implementation/01-front-controller/public/index.php b/implementation/01-front-controller/public/index.php deleted file mode 100644 index 43d37a2..0000000 --- a/implementation/01-front-controller/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "phpstan/phpstan", - "version": "1.4.10", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.4.10" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-14T10:25:45+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/03-error-handler/public/index.php b/implementation/03-error-handler/public/index.php deleted file mode 100644 index 43d37a2..0000000 --- a/implementation/03-error-handler/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (\Throwable $e) { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -throw new \Exception("Ooooopsie"); diff --git a/implementation/04-dev-helpers/.php-cs-fixer.php b/implementation/04-dev-helpers/.php-cs-fixer.php deleted file mode 100644 index 3db1195..0000000 --- a/implementation/04-dev-helpers/.php-cs-fixer.php +++ /dev/null @@ -1,41 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'linebreak_after_opening_tag' => true, - 'native_constant_invocation' => true, - 'native_function_invocation' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - 'ordered_imports' => [ - 'sort_algorithm' => 'alpha', - 'imports_order' => [ - 'const', - 'class', - 'function', - ], - ], - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in(__DIR__ . '/src') - ); \ No newline at end of file diff --git a/implementation/04-dev-helpers/composer.json b/implementation/04-dev-helpers/composer.json deleted file mode 100644 index 995d677..0000000 --- a/implementation/04-dev-helpers/composer.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "scripts": { - "serve": "php -S localhost:1234 -t ./public", - "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", - "phpstan": "./vendor/bin/phpstan analyze", - "style": "./vendor/bin/php-cs-fixer fix" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "php-cs-fixer/shim": "^3.7", - "symfony/var-dumper": "^6.0" - } -} diff --git a/implementation/04-dev-helpers/composer.lock b/implementation/04-dev-helpers/composer.lock deleted file mode 100644 index 8ac2710..0000000 --- a/implementation/04-dev-helpers/composer.lock +++ /dev/null @@ -1,420 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "e956f9f7a53de7c1c149a093926ab371", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "php-cs-fixer/shim", - "version": "v3.7.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" - }, - "time": "2022-03-07T17:02:59+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.4.10", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.4.10" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-14T10:25:45+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/04-dev-helpers/phpstan.neon b/implementation/04-dev-helpers/phpstan.neon deleted file mode 100644 index c2f33f3..0000000 --- a/implementation/04-dev-helpers/phpstan.neon +++ /dev/null @@ -1,4 +0,0 @@ -parameters: - level: 9 - paths: - - src \ No newline at end of file diff --git a/implementation/04-dev-helpers/public/index.php b/implementation/04-dev-helpers/public/index.php deleted file mode 100644 index 32f5eb3..0000000 --- a/implementation/04-dev-helpers/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -pushHandler(new PrettyPageHandler()); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -throw new Exception("Ooooopsie"); diff --git a/implementation/05-http/.php-cs-fixer.php b/implementation/05-http/.php-cs-fixer.php deleted file mode 100644 index 3db1195..0000000 --- a/implementation/05-http/.php-cs-fixer.php +++ /dev/null @@ -1,41 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'linebreak_after_opening_tag' => true, - 'native_constant_invocation' => true, - 'native_function_invocation' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - 'ordered_imports' => [ - 'sort_algorithm' => 'alpha', - 'imports_order' => [ - 'const', - 'class', - 'function', - ], - ], - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in(__DIR__ . '/src') - ); \ No newline at end of file diff --git a/implementation/05-http/composer.json b/implementation/05-http/composer.json deleted file mode 100644 index a576724..0000000 --- a/implementation/05-http/composer.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "scripts": { - "serve": "php -S localhost:1234 -t ./public", - "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", - "phpstan": "./vendor/bin/phpstan analyze", - "style": "./vendor/bin/php-cs-fixer fix" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "php-cs-fixer/shim": "^3.7", - "symfony/var-dumper": "^6.0" - } -} diff --git a/implementation/05-http/composer.lock b/implementation/05-http/composer.lock deleted file mode 100644 index c8f686e..0000000 --- a/implementation/05-http/composer.lock +++ /dev/null @@ -1,627 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "8f46f82d7ff0a4180389107e37ae5ed0", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "php-cs-fixer/shim", - "version": "v3.7.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" - }, - "time": "2022-03-07T17:02:59+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.4.10", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.4.10" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-14T10:25:45+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/05-http/phpstan-baseline.neon b/implementation/05-http/phpstan-baseline.neon deleted file mode 100644 index e69de29..0000000 diff --git a/implementation/05-http/phpstan.neon b/implementation/05-http/phpstan.neon deleted file mode 100644 index c2f33f3..0000000 --- a/implementation/05-http/phpstan.neon +++ /dev/null @@ -1,4 +0,0 @@ -parameters: - level: 9 - paths: - - src \ No newline at end of file diff --git a/implementation/05-http/public/index.php b/implementation/05-http/public/index.php deleted file mode 100644 index 43d37a2..0000000 --- a/implementation/05-http/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -pushHandler(new PrettyPageHandler()); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$request = ServerRequestFactory::fromGlobals(); -$response = new Response(); -$response->getBody()->write('Hello World!'); - -dd($response); - -/** @var string $name */ -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); diff --git a/implementation/06-router/.php-cs-fixer.php b/implementation/06-router/.php-cs-fixer.php deleted file mode 100644 index 6b8a091..0000000 --- a/implementation/06-router/.php-cs-fixer.php +++ /dev/null @@ -1,44 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'linebreak_after_opening_tag' => true, - 'native_constant_invocation' => true, - 'native_function_invocation' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - 'ordered_imports' => [ - 'sort_algorithm' => 'alpha', - 'imports_order' => [ - 'const', - 'class', - 'function', - ], - ], - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config', - ]) - ); \ No newline at end of file diff --git a/implementation/06-router/composer.json b/implementation/06-router/composer.json deleted file mode 100644 index c046545..0000000 --- a/implementation/06-router/composer.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "scripts": { - "serve": "php -S localhost:1234 -t ./public", - "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", - "phpstan": "./vendor/bin/phpstan analyze", - "style": "./vendor/bin/php-cs-fixer fix" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "php-cs-fixer/shim": "^3.7", - "symfony/var-dumper": "^6.0" - } -} diff --git a/implementation/06-router/composer.lock b/implementation/06-router/composer.lock deleted file mode 100644 index 91a0551..0000000 --- a/implementation/06-router/composer.lock +++ /dev/null @@ -1,677 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "d76b01ed13a5b0b42ce69a745090f7d9", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "php-cs-fixer/shim", - "version": "v3.7.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" - }, - "time": "2022-03-07T17:02:59+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.4.10", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.4.10" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-14T10:25:45+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/06-router/config/routes.php b/implementation/06-router/config/routes.php deleted file mode 100644 index baea1f0..0000000 --- a/implementation/06-router/config/routes.php +++ /dev/null @@ -1,17 +0,0 @@ -addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response())->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new \Laminas\Diactoros\Response())->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}; diff --git a/implementation/06-router/phpstan-baseline.neon b/implementation/06-router/phpstan-baseline.neon deleted file mode 100644 index e69de29..0000000 diff --git a/implementation/06-router/phpstan.neon b/implementation/06-router/phpstan.neon deleted file mode 100644 index c2f33f3..0000000 --- a/implementation/06-router/phpstan.neon +++ /dev/null @@ -1,4 +0,0 @@ -parameters: - level: 9 - paths: - - src \ No newline at end of file diff --git a/implementation/06-router/public/index.php b/implementation/06-router/public/index.php deleted file mode 100644 index 43d37a2..0000000 --- a/implementation/06-router/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -pushHandler(new PrettyPageHandler()); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$request = ServerRequestFactory::fromGlobals(); -$response = new Response(); - -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = simpleDispatcher($routeDefinitionCallback); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -switch ($routeInfo[0]) { - case Dispatcher::METHOD_NOT_ALLOWED: - $response = (new Response())->withStatus(405); - $response->getBody()->write('Method not allowed'); - $response = $response->withStatus(405); - break; - case Dispatcher::FOUND: - $handler = $routeInfo[1]; - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - /** @var ResponseInterface $response */ - $response = call_user_func($handler, $request); - break; - case Dispatcher::NOT_FOUND: - default: - $response = (new Response())->withStatus(404); - $response->getBody()->write('Not Found!'); - break; -} - -/** @var string $name */ -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); diff --git a/implementation/07-dispatching-to-class/.php-cs-fixer.php b/implementation/07-dispatching-to-class/.php-cs-fixer.php deleted file mode 100644 index 6b8a091..0000000 --- a/implementation/07-dispatching-to-class/.php-cs-fixer.php +++ /dev/null @@ -1,44 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'linebreak_after_opening_tag' => true, - 'native_constant_invocation' => true, - 'native_function_invocation' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - 'ordered_imports' => [ - 'sort_algorithm' => 'alpha', - 'imports_order' => [ - 'const', - 'class', - 'function', - ], - ], - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config', - ]) - ); \ No newline at end of file diff --git a/implementation/07-dispatching-to-class/composer.json b/implementation/07-dispatching-to-class/composer.json deleted file mode 100644 index 53f35e7..0000000 --- a/implementation/07-dispatching-to-class/composer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "scripts": { - "serve": "php -S localhost:1234 -t ./public", - "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", - "phpstan": "./vendor/bin/phpstan analyze", - "style": "./vendor/bin/php-cs-fixer fix" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "php-cs-fixer/shim": "^3.7", - "symfony/var-dumper": "^6.0" - } -} diff --git a/implementation/07-dispatching-to-class/composer.lock b/implementation/07-dispatching-to-class/composer.lock deleted file mode 100644 index f2fa011..0000000 --- a/implementation/07-dispatching-to-class/composer.lock +++ /dev/null @@ -1,734 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "b84022e8ec4df1e5ee958e6cbfb2307c", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "php-cs-fixer/shim", - "version": "v3.7.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" - }, - "time": "2022-03-07T17:02:59+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.4.10", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.4.10" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-14T10:25:45+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/07-dispatching-to-class/config/routes.php b/implementation/07-dispatching-to-class/config/routes.php deleted file mode 100644 index af63e42..0000000 --- a/implementation/07-dispatching-to-class/config/routes.php +++ /dev/null @@ -1,8 +0,0 @@ -addRoute('GET', '/hello[/{name}]', [\Lubian\NoFramework\Action\Hello::class, 'handle']); - $r->addRoute('GET', '/another-route', [\Lubian\NoFramework\Action\Another::class, 'handle']); -}; diff --git a/implementation/07-dispatching-to-class/phpstan-baseline.neon b/implementation/07-dispatching-to-class/phpstan-baseline.neon deleted file mode 100644 index e69de29..0000000 diff --git a/implementation/07-dispatching-to-class/phpstan.neon b/implementation/07-dispatching-to-class/phpstan.neon deleted file mode 100644 index c2f33f3..0000000 --- a/implementation/07-dispatching-to-class/phpstan.neon +++ /dev/null @@ -1,4 +0,0 @@ -parameters: - level: 9 - paths: - - src \ No newline at end of file diff --git a/implementation/07-dispatching-to-class/public/index.php b/implementation/07-dispatching-to-class/public/index.php deleted file mode 100644 index 43d37a2..0000000 --- a/implementation/07-dispatching-to-class/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - } -} diff --git a/implementation/07-dispatching-to-class/src/Action/Hello.php b/implementation/07-dispatching-to-class/src/Action/Hello.php deleted file mode 100644 index b516c1a..0000000 --- a/implementation/07-dispatching-to-class/src/Action/Hello.php +++ /dev/null @@ -1,21 +0,0 @@ -getAttribute('name', 'Stranger'); - $response = (new Response())->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - } -} diff --git a/implementation/07-dispatching-to-class/src/Action/InternalServerError.php b/implementation/07-dispatching-to-class/src/Action/InternalServerError.php deleted file mode 100644 index 5f2aa5b..0000000 --- a/implementation/07-dispatching-to-class/src/Action/InternalServerError.php +++ /dev/null @@ -1,20 +0,0 @@ -withStatus(500); - $response->getBody()->write('Internal Server Error.'); - return $response; - } -} diff --git a/implementation/07-dispatching-to-class/src/Action/NotAllowed.php b/implementation/07-dispatching-to-class/src/Action/NotAllowed.php deleted file mode 100644 index 799f4e3..0000000 --- a/implementation/07-dispatching-to-class/src/Action/NotAllowed.php +++ /dev/null @@ -1,20 +0,0 @@ -withStatus(405); - $response->getBody()->write('Method Not Allowed'); - return $response; - } -} diff --git a/implementation/07-dispatching-to-class/src/Action/NotFound.php b/implementation/07-dispatching-to-class/src/Action/NotFound.php deleted file mode 100644 index 0a88dc5..0000000 --- a/implementation/07-dispatching-to-class/src/Action/NotFound.php +++ /dev/null @@ -1,20 +0,0 @@ -withStatus(404); - $response->getBody()->write('Page not found'); - return $response; - } -} diff --git a/implementation/07-dispatching-to-class/src/Bootstrap.php b/implementation/07-dispatching-to-class/src/Bootstrap.php deleted file mode 100644 index c13c6fb..0000000 --- a/implementation/07-dispatching-to-class/src/Bootstrap.php +++ /dev/null @@ -1,102 +0,0 @@ -pushHandler(new PrettyPageHandler()); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$request = ServerRequestFactory::fromGlobals(); -$response = new Response(); - -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = simpleDispatcher($routeDefinitionCallback); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -try { - switch ($routeInfo[0]) { - case Dispatcher::METHOD_NOT_ALLOWED: - throw new NotAllowedException(); - case Dispatcher::FOUND: - /** @var RequestHandlerInterface $handler */ - $handler = new $routeInfo[1][0](); - $method = $routeInfo[1][1]; - if (!$handler instanceof RequestHandlerInterface) { - throw new InternalServerErrorException(); - } - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->$method($request); - break; - case Dispatcher::NOT_FOUND: - default: - throw new NotFoundException(); - } -} catch (NotAllowedException) { - $response = (new NotAllowed())->handle($request); -} catch (NotFoundException) { - $response = (new NotFound())->handle($request); -} catch (Exception) { - $response = (new InternalServerError())->handle($request); -} - -/** @var string $name */ -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); diff --git a/implementation/07-dispatching-to-class/src/Exception/InternalServerErrorException.php b/implementation/07-dispatching-to-class/src/Exception/InternalServerErrorException.php deleted file mode 100644 index aa3fddd..0000000 --- a/implementation/07-dispatching-to-class/src/Exception/InternalServerErrorException.php +++ /dev/null @@ -1,11 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'linebreak_after_opening_tag' => true, - 'native_constant_invocation' => true, - 'native_function_invocation' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - 'ordered_imports' => [ - 'sort_algorithm' => 'alpha', - 'imports_order' => [ - 'const', - 'class', - 'function', - ], - ], - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config', - ]) - ); \ No newline at end of file diff --git a/implementation/08-inversion-of-control/composer.json b/implementation/08-inversion-of-control/composer.json deleted file mode 100644 index 53f35e7..0000000 --- a/implementation/08-inversion-of-control/composer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "scripts": { - "serve": "php -S localhost:1234 -t ./public", - "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", - "phpstan": "./vendor/bin/phpstan analyze", - "style": "./vendor/bin/php-cs-fixer fix" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "php-cs-fixer/shim": "^3.7", - "symfony/var-dumper": "^6.0" - } -} diff --git a/implementation/08-inversion-of-control/composer.lock b/implementation/08-inversion-of-control/composer.lock deleted file mode 100644 index f2fa011..0000000 --- a/implementation/08-inversion-of-control/composer.lock +++ /dev/null @@ -1,734 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "b84022e8ec4df1e5ee958e6cbfb2307c", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "php-cs-fixer/shim", - "version": "v3.7.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" - }, - "time": "2022-03-07T17:02:59+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.4.10", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.4.10" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-14T10:25:45+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/08-inversion-of-control/config/routes.php b/implementation/08-inversion-of-control/config/routes.php deleted file mode 100644 index af63e42..0000000 --- a/implementation/08-inversion-of-control/config/routes.php +++ /dev/null @@ -1,8 +0,0 @@ -addRoute('GET', '/hello[/{name}]', [\Lubian\NoFramework\Action\Hello::class, 'handle']); - $r->addRoute('GET', '/another-route', [\Lubian\NoFramework\Action\Another::class, 'handle']); -}; diff --git a/implementation/08-inversion-of-control/phpstan-baseline.neon b/implementation/08-inversion-of-control/phpstan-baseline.neon deleted file mode 100644 index e69de29..0000000 diff --git a/implementation/08-inversion-of-control/phpstan.neon b/implementation/08-inversion-of-control/phpstan.neon deleted file mode 100644 index c2f33f3..0000000 --- a/implementation/08-inversion-of-control/phpstan.neon +++ /dev/null @@ -1,4 +0,0 @@ -parameters: - level: 9 - paths: - - src \ No newline at end of file diff --git a/implementation/08-inversion-of-control/public/index.php b/implementation/08-inversion-of-control/public/index.php deleted file mode 100644 index 43d37a2..0000000 --- a/implementation/08-inversion-of-control/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -response->getBody()->write('This works too!'); - return $this->response; - } -} diff --git a/implementation/08-inversion-of-control/src/Action/Hello.php b/implementation/08-inversion-of-control/src/Action/Hello.php deleted file mode 100644 index b45021e..0000000 --- a/implementation/08-inversion-of-control/src/Action/Hello.php +++ /dev/null @@ -1,20 +0,0 @@ -getAttribute('name', 'Stranger'); - $this->response->getBody()->write('Hello ' . $name . '!'); - return $this->response; - } -} diff --git a/implementation/08-inversion-of-control/src/Action/InternalServerError.php b/implementation/08-inversion-of-control/src/Action/InternalServerError.php deleted file mode 100644 index d1acbb8..0000000 --- a/implementation/08-inversion-of-control/src/Action/InternalServerError.php +++ /dev/null @@ -1,19 +0,0 @@ -response->getBody()->write('Internal Server Error.'); - return $this->response->withStatus(500); - } -} diff --git a/implementation/08-inversion-of-control/src/Action/NotAllowed.php b/implementation/08-inversion-of-control/src/Action/NotAllowed.php deleted file mode 100644 index 83abb60..0000000 --- a/implementation/08-inversion-of-control/src/Action/NotAllowed.php +++ /dev/null @@ -1,19 +0,0 @@ -response->getBody()->write('Method Not Allowed'); - return $this->response->withStatus(405); - } -} diff --git a/implementation/08-inversion-of-control/src/Action/NotFound.php b/implementation/08-inversion-of-control/src/Action/NotFound.php deleted file mode 100644 index ceee42f..0000000 --- a/implementation/08-inversion-of-control/src/Action/NotFound.php +++ /dev/null @@ -1,19 +0,0 @@ -response->getBody()->write('Page not found'); - return $this->response->withStatus(404); - } -} diff --git a/implementation/08-inversion-of-control/src/Bootstrap.php b/implementation/08-inversion-of-control/src/Bootstrap.php deleted file mode 100644 index c7ec6e8..0000000 --- a/implementation/08-inversion-of-control/src/Bootstrap.php +++ /dev/null @@ -1,102 +0,0 @@ -pushHandler(new PrettyPageHandler()); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$request = ServerRequestFactory::fromGlobals(); -$response = new Response(); - -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = simpleDispatcher($routeDefinitionCallback); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -try { - switch ($routeInfo[0]) { - case Dispatcher::METHOD_NOT_ALLOWED: - throw new NotAllowedException(); - case Dispatcher::FOUND: - /** @var RequestHandlerInterface $handler */ - $handler = new $routeInfo[1][0]($response); - $method = $routeInfo[1][1]; - if (!$handler instanceof RequestHandlerInterface) { - throw new InternalServerErrorException(); - } - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->$method($request); - break; - case Dispatcher::NOT_FOUND: - default: - throw new NotFoundException(); - } -} catch (NotAllowedException) { - $response = (new NotAllowed($response))->handle($request); -} catch (NotFoundException) { - $response = (new NotFound($response))->handle($request); -} catch (Exception) { - $response = (new InternalServerError($response))->handle($request); -} - -/** @var string $name */ -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); diff --git a/implementation/08-inversion-of-control/src/Exception/InternalServerErrorException.php b/implementation/08-inversion-of-control/src/Exception/InternalServerErrorException.php deleted file mode 100644 index aa3fddd..0000000 --- a/implementation/08-inversion-of-control/src/Exception/InternalServerErrorException.php +++ /dev/null @@ -1,11 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'linebreak_after_opening_tag' => true, - 'native_constant_invocation' => true, - 'native_function_invocation' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - 'ordered_imports' => [ - 'sort_algorithm' => 'alpha', - 'imports_order' => [ - 'const', - 'class', - 'function', - ], - ], - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config', - ]) - ); \ No newline at end of file diff --git a/implementation/09-dependency-injector/composer.json b/implementation/09-dependency-injector/composer.json deleted file mode 100644 index a38d437..0000000 --- a/implementation/09-dependency-injector/composer.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "scripts": { - "serve": "php -S localhost:1234 -t ./public", - "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "style": "./vendor/bin/php-cs-fixer fix" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "php-cs-fixer/shim": "^3.7", - "symfony/var-dumper": "^6.0" - } -} diff --git a/implementation/09-dependency-injector/composer.lock b/implementation/09-dependency-injector/composer.lock deleted file mode 100644 index cb0aa21..0000000 --- a/implementation/09-dependency-injector/composer.lock +++ /dev/null @@ -1,1020 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "176033a9d3cd7179cb7bb9608fa24210", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "php-cs-fixer/shim", - "version": "v3.7.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" - }, - "time": "2022-03-07T17:02:59+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.4.10", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.4.10" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-14T10:25:45+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/09-dependency-injector/config/dependencies.php b/implementation/09-dependency-injector/config/dependencies.php deleted file mode 100644 index ac1d962..0000000 --- a/implementation/09-dependency-injector/config/dependencies.php +++ /dev/null @@ -1,11 +0,0 @@ -addDefinitions([ - \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), - \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), -]); - -return $builder->build(); diff --git a/implementation/09-dependency-injector/config/routes.php b/implementation/09-dependency-injector/config/routes.php deleted file mode 100644 index af63e42..0000000 --- a/implementation/09-dependency-injector/config/routes.php +++ /dev/null @@ -1,8 +0,0 @@ -addRoute('GET', '/hello[/{name}]', [\Lubian\NoFramework\Action\Hello::class, 'handle']); - $r->addRoute('GET', '/another-route', [\Lubian\NoFramework\Action\Another::class, 'handle']); -}; diff --git a/implementation/09-dependency-injector/phpstan-baseline.neon b/implementation/09-dependency-injector/phpstan-baseline.neon deleted file mode 100644 index fe0b762..0000000 --- a/implementation/09-dependency-injector/phpstan-baseline.neon +++ /dev/null @@ -1,7 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Cannot call method handle\\(\\) on mixed\\.$#" - count: 3 - path: src/Bootstrap.php - diff --git a/implementation/09-dependency-injector/phpstan.neon b/implementation/09-dependency-injector/phpstan.neon deleted file mode 100644 index 28914db..0000000 --- a/implementation/09-dependency-injector/phpstan.neon +++ /dev/null @@ -1,7 +0,0 @@ -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src \ No newline at end of file diff --git a/implementation/09-dependency-injector/public/index.php b/implementation/09-dependency-injector/public/index.php deleted file mode 100644 index 43d37a2..0000000 --- a/implementation/09-dependency-injector/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -response->getBody()->write('This works too!'); - return $this->response; - } -} diff --git a/implementation/09-dependency-injector/src/Action/Hello.php b/implementation/09-dependency-injector/src/Action/Hello.php deleted file mode 100644 index b45021e..0000000 --- a/implementation/09-dependency-injector/src/Action/Hello.php +++ /dev/null @@ -1,20 +0,0 @@ -getAttribute('name', 'Stranger'); - $this->response->getBody()->write('Hello ' . $name . '!'); - return $this->response; - } -} diff --git a/implementation/09-dependency-injector/src/Action/InternalServerError.php b/implementation/09-dependency-injector/src/Action/InternalServerError.php deleted file mode 100644 index d1acbb8..0000000 --- a/implementation/09-dependency-injector/src/Action/InternalServerError.php +++ /dev/null @@ -1,19 +0,0 @@ -response->getBody()->write('Internal Server Error.'); - return $this->response->withStatus(500); - } -} diff --git a/implementation/09-dependency-injector/src/Action/NotAllowed.php b/implementation/09-dependency-injector/src/Action/NotAllowed.php deleted file mode 100644 index 83abb60..0000000 --- a/implementation/09-dependency-injector/src/Action/NotAllowed.php +++ /dev/null @@ -1,19 +0,0 @@ -response->getBody()->write('Method Not Allowed'); - return $this->response->withStatus(405); - } -} diff --git a/implementation/09-dependency-injector/src/Action/NotFound.php b/implementation/09-dependency-injector/src/Action/NotFound.php deleted file mode 100644 index ceee42f..0000000 --- a/implementation/09-dependency-injector/src/Action/NotFound.php +++ /dev/null @@ -1,19 +0,0 @@ -response->getBody()->write('Page not found'); - return $this->response->withStatus(404); - } -} diff --git a/implementation/09-dependency-injector/src/Bootstrap.php b/implementation/09-dependency-injector/src/Bootstrap.php deleted file mode 100644 index a059b66..0000000 --- a/implementation/09-dependency-injector/src/Bootstrap.php +++ /dev/null @@ -1,106 +0,0 @@ -pushHandler(new PrettyPageHandler()); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -/** @var ContainerInterface $container */ -$container = require __DIR__ . '/../config/dependencies.php'; -/** @var ServerRequestInterface $request */ -$request = $container->get(ServerRequestInterface::class); - -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = simpleDispatcher($routeDefinitionCallback); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -try { - switch ($routeInfo[0]) { - case Dispatcher::METHOD_NOT_ALLOWED: - throw new NotAllowedException(); - case Dispatcher::FOUND: - /** @var RequestHandlerInterface $handler */ - $handler = $container->get($routeInfo[1][0]); - $method = $routeInfo[1][1]; - if (!$handler instanceof RequestHandlerInterface) { - throw new InternalServerErrorException(); - } - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->$method($request); - break; - case Dispatcher::NOT_FOUND: - default: - throw new NotFoundException(); - } -} catch (NotAllowedException) { - $response = $container->get(NotAllowed::class)->handle($request); -} catch (NotFoundException) { - $response = $container->get(NotFound::class)->handle($request); -} catch (Exception) { - $response = $container->get(InternalServerError::class)->handle($request); -} - -/** @var string $name */ -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); diff --git a/implementation/09-dependency-injector/src/Exception/InternalServerErrorException.php b/implementation/09-dependency-injector/src/Exception/InternalServerErrorException.php deleted file mode 100644 index aa3fddd..0000000 --- a/implementation/09-dependency-injector/src/Exception/InternalServerErrorException.php +++ /dev/null @@ -1,11 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'linebreak_after_opening_tag' => true, - 'native_constant_invocation' => true, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - 'ordered_imports' => [ - 'sort_algorithm' => 'alpha', - 'imports_order' => [ - 'const', - 'class', - 'function', - ], - ], - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config', - ]) - ); \ No newline at end of file diff --git a/implementation/10-invoker/composer.json b/implementation/10-invoker/composer.json deleted file mode 100644 index 3c17ae5..0000000 --- a/implementation/10-invoker/composer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "lubian/no-framework", - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0" - }, - "scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "style": "./vendor/bin/php-cs-fixer fix" - } -} diff --git a/implementation/10-invoker/composer.lock b/implementation/10-invoker/composer.lock deleted file mode 100644 index d300ec9..0000000 --- a/implementation/10-invoker/composer.lock +++ /dev/null @@ -1,1020 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "c8641669dc93f9bfa8f95f20a8598451", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.4.10", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.4.10" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-14T10:25:45+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/10-invoker/config/dependencies.php b/implementation/10-invoker/config/dependencies.php deleted file mode 100644 index 3708e0d..0000000 --- a/implementation/10-invoker/config/dependencies.php +++ /dev/null @@ -1,12 +0,0 @@ -addDefinitions([ - \Psr\Http\Message\ResponseInterface::class => fn () => new \Laminas\Diactoros\Response(), - \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), - \Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), -]); - -return $builder->build(); diff --git a/implementation/10-invoker/config/routes.php b/implementation/10-invoker/config/routes.php deleted file mode 100644 index a60931c..0000000 --- a/implementation/10-invoker/config/routes.php +++ /dev/null @@ -1,18 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); - $r->addRoute( - 'GET', - '/', - fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello') - ); -}; diff --git a/implementation/10-invoker/phpstan.neon b/implementation/10-invoker/phpstan.neon deleted file mode 100644 index c6ce9bd..0000000 --- a/implementation/10-invoker/phpstan.neon +++ /dev/null @@ -1,5 +0,0 @@ -parameters: - level: 9 - paths: - - src - - config \ No newline at end of file diff --git a/implementation/10-invoker/public/favicon.ico b/implementation/10-invoker/public/favicon.ico deleted file mode 100644 index 09499b8b3b3201e0f50088e3ac42e167778d1153..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/10-invoker/public/index.php b/implementation/10-invoker/public/index.php deleted file mode 100644 index db23804..0000000 --- a/implementation/10-invoker/public/index.php +++ /dev/null @@ -1,6 +0,0 @@ -withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - $nowString = $now->get()->format('H:i:s'); - $response->getBody()->write(' The Time is ' . $nowString); - return $response; - } -} diff --git a/implementation/10-invoker/src/Action/Other.php b/implementation/10-invoker/src/Action/Other.php deleted file mode 100644 index 3563983..0000000 --- a/implementation/10-invoker/src/Action/Other.php +++ /dev/null @@ -1,20 +0,0 @@ -response->withStatus(200); - $response->getBody()->write('This works too'); - return $response; - } -} diff --git a/implementation/10-invoker/src/Bootstrap.php b/implementation/10-invoker/src/Bootstrap.php deleted file mode 100644 index d3bfc96..0000000 --- a/implementation/10-invoker/src/Bootstrap.php +++ /dev/null @@ -1,110 +0,0 @@ -pushHandler(new PrettyPageHandler()); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log("ERROR: " . $e->getMessage(), $e->getCode()); - echo 'AN ERROR HAPPENED!!!'; - }); -} - -$whoops->register(); - -/** @var ContainerInterface $container */ -$container = require __DIR__ . '/../config/dependencies.php'; - -/** @var InvokerInterface $invoker */ -$invoker = $container->get(InvokerInterface::class); - -/** @var ServerRequestInterface $request */ -$request = $container->get(ServerRequestInterface::class); - -/** @var ResponseInterface $response */ -$response = $container->get(ResponseInterface::class); - -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = simpleDispatcher($routeDefinitionCallback); - -$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri()->getPath()); - -try { - switch ($routeInfo[0]) { - case Dispatcher::FOUND: - $handler = $routeInfo[1]; - $args = $routeInfo[2]; - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $args['request'] = $request; - $response = $container->call($handler, $args); - break; - case Dispatcher::METHOD_NOT_ALLOWED: - throw new MethodNotAllowed(); - case Dispatcher::NOT_FOUND: - default: - throw new NotFound(); - } -} catch (NotFound) { - $response = $response->withStatus(404); - $response->getBody()->write('Not Found'); -} catch (MethodNotAllowed) { - $response = $response->withStatus(405); - $response->getBody()->write('Method not Allowed'); -} catch (Exception $e) { - throw new InternalServerError($e->getMessage(), $e->getCode(), $e); -} - - - -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase(), -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); diff --git a/implementation/10-invoker/src/Exception/InternalServerError.php b/implementation/10-invoker/src/Exception/InternalServerError.php deleted file mode 100644 index a7b627b..0000000 --- a/implementation/10-invoker/src/Exception/InternalServerError.php +++ /dev/null @@ -1,11 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config' - ]) - ); \ No newline at end of file diff --git a/implementation/11-templating/.phpcs.xml.dist b/implementation/11-templating/.phpcs.xml.dist deleted file mode 100644 index 3b433f6..0000000 --- a/implementation/11-templating/.phpcs.xml.dist +++ /dev/null @@ -1,9 +0,0 @@ - - - - - src - config - - - \ No newline at end of file diff --git a/implementation/11-templating/composer.json b/implementation/11-templating/composer.json deleted file mode 100644 index 2c61bdd..0000000 --- a/implementation/11-templating/composer.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3", - "mustache/mustache": "^2.14" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require-dev": { - "phpstan/phpstan": "^1.5", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0", - "squizlabs/php_codesniffer": "^3.6", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1", - "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0" - }, - "config": { - "allow-plugins": { - "phpstan/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" - } -} diff --git a/implementation/11-templating/composer.lock b/implementation/11-templating/composer.lock deleted file mode 100644 index 37ee56e..0000000 --- a/implementation/11-templating/composer.lock +++ /dev/null @@ -1,1550 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "e237b9a5b4210f235b1609a7ce3e065a", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "doctrine/coding-standard", - "version": "8.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/coding-standard.git", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "slevomat/coding-standard": "^6.4.1", - "squizlabs/php_codesniffer": "^3.5.8" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Steve Müller", - "email": "st.mueller@dzh-online.de" - } - ], - "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", - "keywords": [ - "checks", - "code", - "coding", - "cs", - "doctrine", - "rules", - "sniffer", - "sniffs", - "standard", - "style" - ], - "support": { - "issues": "https://github.com/doctrine/coding-standard/issues", - "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" - }, - "time": "2021-04-03T10:54:55+00:00" - }, - { - "name": "mnapoli/hard-mode", - "version": "0.3.0", - "source": { - "type": "git", - "url": "https://github.com/mnapoli/hard-mode.git", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", - "shasum": "" - }, - "require": { - "doctrine/coding-standard": "^8.0" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Hard mode for PHP", - "support": { - "issues": "https://github.com/mnapoli/hard-mode/issues", - "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" - }, - "time": "2020-10-12T07:54:37+00:00" - }, - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.9", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.26", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/master" - }, - "time": "2020-08-03T20:32:43+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.0" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-24T18:18:00+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^1.2.0" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" - }, - "time": "2021-11-18T09:30:29+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "6.4.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpstan/phpstan": "0.12.48", - "phpstan/phpstan-deprecation-rules": "0.12.5", - "phpstan/phpstan-phpunit": "0.12.16", - "phpstan/phpstan-strict-rules": "0.12.5", - "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2020-10-05T12:39:37+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/11-templating/config/dependencies.php b/implementation/11-templating/config/dependencies.php deleted file mode 100644 index c3e65a8..0000000 --- a/implementation/11-templating/config/dependencies.php +++ /dev/null @@ -1,32 +0,0 @@ -addDefinitions([ - Settings::class => fn () => require __DIR__ . '/settings.php', - ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - Mustache_Loader_FilesystemLoader::class => function (Settings $s) { - return new Mustache_Loader_FilesystemLoader( - $s->templateDir, - [ - 'extension' => $s->templateExtension, - ], - ); - }, - Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $mfl) => new Mustache_Engine(['loader' => $mfl]), -]); - -return $builder->build(); diff --git a/implementation/11-templating/config/routes.php b/implementation/11-templating/config/routes.php deleted file mode 100644 index 1bc00bc..0000000 --- a/implementation/11-templating/config/routes.php +++ /dev/null @@ -1,12 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); - $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -}; diff --git a/implementation/11-templating/config/settings.php b/implementation/11-templating/config/settings.php deleted file mode 100644 index ad2ea0c..0000000 --- a/implementation/11-templating/config/settings.php +++ /dev/null @@ -1,9 +0,0 @@ -aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/11-templating/public/index.php b/implementation/11-templating/public/index.php deleted file mode 100644 index d93da3a..0000000 --- a/implementation/11-templating/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render('hello', $data); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/11-templating/src/Action/Other.php b/implementation/11-templating/src/Action/Other.php deleted file mode 100644 index 895796e..0000000 --- a/implementation/11-templating/src/Action/Other.php +++ /dev/null @@ -1,19 +0,0 @@ -getBody(); - - $body->write('This works too!'); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/11-templating/src/Bootstrap.php b/implementation/11-templating/src/Bootstrap.php deleted file mode 100644 index 2a45e08..0000000 --- a/implementation/11-templating/src/Bootstrap.php +++ /dev/null @@ -1,110 +0,0 @@ -pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$container = require __DIR__ . '/../config/dependencies.php'; -assert($container instanceof ContainerInterface); - -$request = $container->get(ServerRequestInterface::class); -assert($request instanceof ServerRequestInterface); - -$responseFactory = $container->get(ResponseFactory::class); -assert($responseFactory instanceof ResponseFactory); - -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = simpleDispatcher($routeDefinitionCallback); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -try { - switch ($routeInfo[0]) { - case Dispatcher::FOUND: - $handler = $routeInfo[1]; - $vars = $routeInfo[2] ?? []; - foreach ($vars as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $vars['request'] = $request; - $invoker = $container->get(InvokerInterface::class); - assert($invoker instanceof InvokerInterface); - $response = $invoker->call($handler, $vars); - assert($response instanceof ResponseInterface); - break; - case Dispatcher::METHOD_NOT_ALLOWED: - throw new MethodNotAllowed; - - case Dispatcher::NOT_FOUND: - default: - throw new NotFound; - } -} catch (MethodNotAllowed) { - $response = $responseFactory->createResponse(405); -} catch (NotFound) { - $response = $responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); -} catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); -} - -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); diff --git a/implementation/11-templating/src/Exception/InternalServerError.php b/implementation/11-templating/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/implementation/11-templating/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ -engine->render($template, $data); - } -} diff --git a/implementation/11-templating/src/Template/Renderer.php b/implementation/11-templating/src/Template/Renderer.php deleted file mode 100644 index ff916ed..0000000 --- a/implementation/11-templating/src/Template/Renderer.php +++ /dev/null @@ -1,11 +0,0 @@ - $data - */ - public function render(string $template, array $data = []): string; -} diff --git a/implementation/11-templating/templates/hello.html b/implementation/11-templating/templates/hello.html deleted file mode 100644 index 0e21f2a..0000000 --- a/implementation/11-templating/templates/hello.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Hello World - - -

Hello {{name}}

-

The time is {{now}}

- - \ No newline at end of file diff --git a/implementation/12-configuration/.php-cs-fixer.php b/implementation/12-configuration/.php-cs-fixer.php deleted file mode 100644 index 705a7d7..0000000 --- a/implementation/12-configuration/.php-cs-fixer.php +++ /dev/null @@ -1,38 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config' - ]) - ); \ No newline at end of file diff --git a/implementation/12-configuration/.phpcs.xml.dist b/implementation/12-configuration/.phpcs.xml.dist deleted file mode 100644 index 3b433f6..0000000 --- a/implementation/12-configuration/.phpcs.xml.dist +++ /dev/null @@ -1,9 +0,0 @@ - - - - - src - config - - - \ No newline at end of file diff --git a/implementation/12-configuration/composer.json b/implementation/12-configuration/composer.json deleted file mode 100644 index 2c61bdd..0000000 --- a/implementation/12-configuration/composer.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3", - "mustache/mustache": "^2.14" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require-dev": { - "phpstan/phpstan": "^1.5", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0", - "squizlabs/php_codesniffer": "^3.6", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1", - "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0" - }, - "config": { - "allow-plugins": { - "phpstan/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" - } -} diff --git a/implementation/12-configuration/composer.lock b/implementation/12-configuration/composer.lock deleted file mode 100644 index 37ee56e..0000000 --- a/implementation/12-configuration/composer.lock +++ /dev/null @@ -1,1550 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "e237b9a5b4210f235b1609a7ce3e065a", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "doctrine/coding-standard", - "version": "8.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/coding-standard.git", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "slevomat/coding-standard": "^6.4.1", - "squizlabs/php_codesniffer": "^3.5.8" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Steve Müller", - "email": "st.mueller@dzh-online.de" - } - ], - "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", - "keywords": [ - "checks", - "code", - "coding", - "cs", - "doctrine", - "rules", - "sniffer", - "sniffs", - "standard", - "style" - ], - "support": { - "issues": "https://github.com/doctrine/coding-standard/issues", - "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" - }, - "time": "2021-04-03T10:54:55+00:00" - }, - { - "name": "mnapoli/hard-mode", - "version": "0.3.0", - "source": { - "type": "git", - "url": "https://github.com/mnapoli/hard-mode.git", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", - "shasum": "" - }, - "require": { - "doctrine/coding-standard": "^8.0" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Hard mode for PHP", - "support": { - "issues": "https://github.com/mnapoli/hard-mode/issues", - "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" - }, - "time": "2020-10-12T07:54:37+00:00" - }, - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.9", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.26", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/master" - }, - "time": "2020-08-03T20:32:43+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.0" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-24T18:18:00+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^1.2.0" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" - }, - "time": "2021-11-18T09:30:29+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "6.4.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpstan/phpstan": "0.12.48", - "phpstan/phpstan-deprecation-rules": "0.12.5", - "phpstan/phpstan-phpunit": "0.12.16", - "phpstan/phpstan-strict-rules": "0.12.5", - "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2020-10-05T12:39:37+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/12-configuration/config/dependencies.php b/implementation/12-configuration/config/dependencies.php deleted file mode 100644 index 2957edb..0000000 --- a/implementation/12-configuration/config/dependencies.php +++ /dev/null @@ -1,22 +0,0 @@ - fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), -]; diff --git a/implementation/12-configuration/config/routes.php b/implementation/12-configuration/config/routes.php deleted file mode 100644 index 1bc00bc..0000000 --- a/implementation/12-configuration/config/routes.php +++ /dev/null @@ -1,12 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); - $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -}; diff --git a/implementation/12-configuration/config/settings.php b/implementation/12-configuration/config/settings.php deleted file mode 100644 index 0dc42b6..0000000 --- a/implementation/12-configuration/config/settings.php +++ /dev/null @@ -1,10 +0,0 @@ -aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/12-configuration/public/index.php b/implementation/12-configuration/public/index.php deleted file mode 100644 index d93da3a..0000000 --- a/implementation/12-configuration/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render('hello', $data); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/12-configuration/src/Action/Other.php b/implementation/12-configuration/src/Action/Other.php deleted file mode 100644 index 895796e..0000000 --- a/implementation/12-configuration/src/Action/Other.php +++ /dev/null @@ -1,19 +0,0 @@ -getBody(); - - $body->write('This works too!'); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/12-configuration/src/Bootstrap.php b/implementation/12-configuration/src/Bootstrap.php deleted file mode 100644 index f47341e..0000000 --- a/implementation/12-configuration/src/Bootstrap.php +++ /dev/null @@ -1,111 +0,0 @@ -getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - - -$request = $container->get(ServerRequestInterface::class); -assert($request instanceof ServerRequestInterface); - -$responseFactory = $container->get(ResponseFactory::class); -assert($responseFactory instanceof ResponseFactory); - -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = simpleDispatcher($routeDefinitionCallback); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -try { - switch ($routeInfo[0]) { - case Dispatcher::FOUND: - $handler = $routeInfo[1]; - $vars = $routeInfo[2] ?? []; - foreach ($vars as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $vars['request'] = $request; - $invoker = $container->get(InvokerInterface::class); - assert($invoker instanceof InvokerInterface); - $response = $invoker->call($handler, $vars); - assert($response instanceof ResponseInterface); - break; - case Dispatcher::METHOD_NOT_ALLOWED: - throw new MethodNotAllowed; - - case Dispatcher::NOT_FOUND: - default: - throw new NotFound; - } -} catch (MethodNotAllowed) { - $response = $responseFactory->createResponse(405); -} catch (NotFound) { - $response = $responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); -} catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); -} - -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); diff --git a/implementation/12-configuration/src/Exception/InternalServerError.php b/implementation/12-configuration/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/implementation/12-configuration/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ -filePath; - } -} diff --git a/implementation/12-configuration/src/Factory/SettingsContainerProvider.php b/implementation/12-configuration/src/Factory/SettingsContainerProvider.php deleted file mode 100644 index 20609bf..0000000 --- a/implementation/12-configuration/src/Factory/SettingsContainerProvider.php +++ /dev/null @@ -1,25 +0,0 @@ -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn () => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} diff --git a/implementation/12-configuration/src/Factory/SettingsProvider.php b/implementation/12-configuration/src/Factory/SettingsProvider.php deleted file mode 100644 index ce1c5f0..0000000 --- a/implementation/12-configuration/src/Factory/SettingsProvider.php +++ /dev/null @@ -1,10 +0,0 @@ -engine->render($template, $data); - } -} diff --git a/implementation/12-configuration/src/Template/Renderer.php b/implementation/12-configuration/src/Template/Renderer.php deleted file mode 100644 index ff916ed..0000000 --- a/implementation/12-configuration/src/Template/Renderer.php +++ /dev/null @@ -1,11 +0,0 @@ - $data - */ - public function render(string $template, array $data = []): string; -} diff --git a/implementation/12-configuration/templates/hello.html b/implementation/12-configuration/templates/hello.html deleted file mode 100644 index 0e21f2a..0000000 --- a/implementation/12-configuration/templates/hello.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Hello World - - -

Hello {{name}}

-

The time is {{now}}

- - \ No newline at end of file diff --git a/implementation/13-refactoring/.php-cs-fixer.php b/implementation/13-refactoring/.php-cs-fixer.php deleted file mode 100644 index 705a7d7..0000000 --- a/implementation/13-refactoring/.php-cs-fixer.php +++ /dev/null @@ -1,38 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config' - ]) - ); \ No newline at end of file diff --git a/implementation/13-refactoring/.phpcs.xml.dist b/implementation/13-refactoring/.phpcs.xml.dist deleted file mode 100644 index 3b433f6..0000000 --- a/implementation/13-refactoring/.phpcs.xml.dist +++ /dev/null @@ -1,9 +0,0 @@ - - - - - src - config - - - \ No newline at end of file diff --git a/implementation/13-refactoring/composer.json b/implementation/13-refactoring/composer.json deleted file mode 100644 index 92e7538..0000000 --- a/implementation/13-refactoring/composer.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3", - "mustache/mustache": "^2.14", - "psr/http-server-middleware": "^1.0" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require-dev": { - "phpstan/phpstan": "^1.5", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0", - "squizlabs/php_codesniffer": "^3.6", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1", - "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0" - }, - "config": { - "allow-plugins": { - "phpstan/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" - } -} diff --git a/implementation/13-refactoring/composer.lock b/implementation/13-refactoring/composer.lock deleted file mode 100644 index c3bb867..0000000 --- a/implementation/13-refactoring/composer.lock +++ /dev/null @@ -1,1607 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "eb13263f65ee72240c5f25ab82caddd0", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/http-server-middleware", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-middleware.git", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0", - "psr/http-server-handler": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side middleware", - "keywords": [ - "http", - "http-interop", - "middleware", - "psr", - "psr-15", - "psr-7", - "request", - "response" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-middleware/issues", - "source": "https://github.com/php-fig/http-server-middleware/tree/master" - }, - "time": "2018-10-30T17:12:04+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "doctrine/coding-standard", - "version": "8.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/coding-standard.git", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "slevomat/coding-standard": "^6.4.1", - "squizlabs/php_codesniffer": "^3.5.8" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Steve Müller", - "email": "st.mueller@dzh-online.de" - } - ], - "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", - "keywords": [ - "checks", - "code", - "coding", - "cs", - "doctrine", - "rules", - "sniffer", - "sniffs", - "standard", - "style" - ], - "support": { - "issues": "https://github.com/doctrine/coding-standard/issues", - "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" - }, - "time": "2021-04-03T10:54:55+00:00" - }, - { - "name": "mnapoli/hard-mode", - "version": "0.3.0", - "source": { - "type": "git", - "url": "https://github.com/mnapoli/hard-mode.git", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", - "shasum": "" - }, - "require": { - "doctrine/coding-standard": "^8.0" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Hard mode for PHP", - "support": { - "issues": "https://github.com/mnapoli/hard-mode/issues", - "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" - }, - "time": "2020-10-12T07:54:37+00:00" - }, - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.9", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.26", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/master" - }, - "time": "2020-08-03T20:32:43+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.0" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-24T18:18:00+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^1.2.0" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" - }, - "time": "2021-11-18T09:30:29+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "6.4.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpstan/phpstan": "0.12.48", - "phpstan/phpstan-deprecation-rules": "0.12.5", - "phpstan/phpstan-phpunit": "0.12.16", - "phpstan/phpstan-strict-rules": "0.12.5", - "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2020-10-05T12:39:37+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/13-refactoring/config/dependencies.php b/implementation/13-refactoring/config/dependencies.php deleted file mode 100644 index c26cbe3..0000000 --- a/implementation/13-refactoring/config/dependencies.php +++ /dev/null @@ -1,39 +0,0 @@ - fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, - Emitter::class => fn (BasicEmitter $e) => $e, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), - RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, -]; diff --git a/implementation/13-refactoring/config/routes.php b/implementation/13-refactoring/config/routes.php deleted file mode 100644 index 1bc00bc..0000000 --- a/implementation/13-refactoring/config/routes.php +++ /dev/null @@ -1,12 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); - $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -}; diff --git a/implementation/13-refactoring/config/settings.php b/implementation/13-refactoring/config/settings.php deleted file mode 100644 index 0dc42b6..0000000 --- a/implementation/13-refactoring/config/settings.php +++ /dev/null @@ -1,10 +0,0 @@ -aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/13-refactoring/public/index.php b/implementation/13-refactoring/public/index.php deleted file mode 100644 index d93da3a..0000000 --- a/implementation/13-refactoring/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render('hello', $data); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/13-refactoring/src/Action/Other.php b/implementation/13-refactoring/src/Action/Other.php deleted file mode 100644 index 895796e..0000000 --- a/implementation/13-refactoring/src/Action/Other.php +++ /dev/null @@ -1,19 +0,0 @@ -getBody(); - - $body->write('This works too!'); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/13-refactoring/src/Bootstrap.php b/implementation/13-refactoring/src/Bootstrap.php deleted file mode 100644 index 2a3cc85..0000000 --- a/implementation/13-refactoring/src/Bootstrap.php +++ /dev/null @@ -1,40 +0,0 @@ -getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); diff --git a/implementation/13-refactoring/src/Exception/InternalServerError.php b/implementation/13-refactoring/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/implementation/13-refactoring/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ -factory::fromGlobals(); - } - - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} diff --git a/implementation/13-refactoring/src/Factory/FileSystemSettingsProvider.php b/implementation/13-refactoring/src/Factory/FileSystemSettingsProvider.php deleted file mode 100644 index 3a847ec..0000000 --- a/implementation/13-refactoring/src/Factory/FileSystemSettingsProvider.php +++ /dev/null @@ -1,18 +0,0 @@ -filePath; - } -} diff --git a/implementation/13-refactoring/src/Factory/RequestFactory.php b/implementation/13-refactoring/src/Factory/RequestFactory.php deleted file mode 100644 index 2b17abc..0000000 --- a/implementation/13-refactoring/src/Factory/RequestFactory.php +++ /dev/null @@ -1,11 +0,0 @@ -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn () => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} diff --git a/implementation/13-refactoring/src/Factory/SettingsProvider.php b/implementation/13-refactoring/src/Factory/SettingsProvider.php deleted file mode 100644 index ce1c5f0..0000000 --- a/implementation/13-refactoring/src/Factory/SettingsProvider.php +++ /dev/null @@ -1,10 +0,0 @@ -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - - echo $response->getBody(); - } -} diff --git a/implementation/13-refactoring/src/Http/Emitter.php b/implementation/13-refactoring/src/Http/Emitter.php deleted file mode 100644 index ce4c035..0000000 --- a/implementation/13-refactoring/src/Http/Emitter.php +++ /dev/null @@ -1,10 +0,0 @@ -getAttribute($this->routeAttributeName, false); - assert($handler !== false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} diff --git a/implementation/13-refactoring/src/Http/RouteMiddleware.php b/implementation/13-refactoring/src/Http/RouteMiddleware.php deleted file mode 100644 index e3df6f8..0000000 --- a/implementation/13-refactoring/src/Http/RouteMiddleware.php +++ /dev/null @@ -1,69 +0,0 @@ -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} diff --git a/implementation/13-refactoring/src/Http/RoutedRequestHandler.php b/implementation/13-refactoring/src/Http/RoutedRequestHandler.php deleted file mode 100644 index a7407c9..0000000 --- a/implementation/13-refactoring/src/Http/RoutedRequestHandler.php +++ /dev/null @@ -1,10 +0,0 @@ -routeMiddleware->process($request, $this->handler); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} diff --git a/implementation/13-refactoring/src/Service/Time/Now.php b/implementation/13-refactoring/src/Service/Time/Now.php deleted file mode 100644 index 79224b3..0000000 --- a/implementation/13-refactoring/src/Service/Time/Now.php +++ /dev/null @@ -1,10 +0,0 @@ -engine->render($template, $data); - } -} diff --git a/implementation/13-refactoring/src/Template/Renderer.php b/implementation/13-refactoring/src/Template/Renderer.php deleted file mode 100644 index ff916ed..0000000 --- a/implementation/13-refactoring/src/Template/Renderer.php +++ /dev/null @@ -1,11 +0,0 @@ - $data - */ - public function render(string $template, array $data = []): string; -} diff --git a/implementation/13-refactoring/templates/hello.html b/implementation/13-refactoring/templates/hello.html deleted file mode 100644 index 0e21f2a..0000000 --- a/implementation/13-refactoring/templates/hello.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Hello World - - -

Hello {{name}}

-

The time is {{now}}

- - \ No newline at end of file diff --git a/implementation/14-middleware/.php-cs-fixer.php b/implementation/14-middleware/.php-cs-fixer.php deleted file mode 100644 index 705a7d7..0000000 --- a/implementation/14-middleware/.php-cs-fixer.php +++ /dev/null @@ -1,38 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config' - ]) - ); \ No newline at end of file diff --git a/implementation/14-middleware/.phpcs.xml.dist b/implementation/14-middleware/.phpcs.xml.dist deleted file mode 100644 index 3b433f6..0000000 --- a/implementation/14-middleware/.phpcs.xml.dist +++ /dev/null @@ -1,9 +0,0 @@ - - - - - src - config - - - \ No newline at end of file diff --git a/implementation/14-middleware/composer.json b/implementation/14-middleware/composer.json deleted file mode 100644 index a1372b3..0000000 --- a/implementation/14-middleware/composer.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3", - "mustache/mustache": "^2.14", - "psr/http-server-middleware": "^1.0", - "middlewares/trailing-slash": "^2.0", - "middlewares/whoops": "^2.0" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require-dev": { - "phpstan/phpstan": "^1.5", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0", - "squizlabs/php_codesniffer": "^3.6", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1", - "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0", - "psalm/phar": "^4.22" - }, - "config": { - "allow-plugins": { - "phpstan/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" - } -} diff --git a/implementation/14-middleware/composer.lock b/implementation/14-middleware/composer.lock deleted file mode 100644 index 3ac6853..0000000 --- a/implementation/14-middleware/composer.lock +++ /dev/null @@ -1,1815 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "580bbe25eceb19e89a36dc4e5541d44c", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "middlewares/trailing-slash", - "version": "v2.0.1", - "source": { - "type": "git", - "url": "https://github.com/middlewares/trailing-slash.git", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", - "shasum": "" - }, - "require": { - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to normalize the trailing slash of the uri path", - "homepage": "https://github.com/middlewares/trailing-slash", - "keywords": [ - "http", - "middleware", - "normalize", - "path", - "psr-15", - "psr-7", - "slash" - ], - "support": { - "issues": "https://github.com/middlewares/trailing-slash/issues", - "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" - }, - "time": "2020-12-02T00:06:55+00:00" - }, - { - "name": "middlewares/utils", - "version": "v3.3.0", - "source": { - "type": "git", - "url": "https://github.com/middlewares/utils.git", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^v2.16", - "guzzlehttp/psr7": "^2.0", - "laminas/laminas-diactoros": "^2.4", - "nyholm/psr7": "^1.0", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "slim/psr7": "^1.4", - "squizlabs/php_codesniffer": "^3.5", - "sunrise/http-message": "^1.0", - "sunrise/http-server-request": "^1.0", - "sunrise/stream": "^1.0.15", - "sunrise/uri": "^1.0.15" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\Utils\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Common utils for PSR-15 middleware packages", - "homepage": "https://github.com/middlewares/utils", - "keywords": [ - "PSR-11", - "http", - "middleware", - "psr-15", - "psr-17", - "psr-7" - ], - "support": { - "issues": "https://github.com/middlewares/utils/issues", - "source": "https://github.com/middlewares/utils/tree/v3.3.0" - }, - "time": "2021-07-04T17:56:23+00:00" - }, - { - "name": "middlewares/whoops", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/middlewares/whoops.git", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", - "shasum": "" - }, - "require": { - "filp/whoops": "^2.5", - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "eloquent/phony-phpunit": "^5.0 || ^7.0", - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to use Whoops as error handler", - "homepage": "https://github.com/middlewares/whoops", - "keywords": [ - "error", - "http", - "middleware", - "psr-15", - "psr-7", - "server", - "whoops" - ], - "support": { - "issues": "https://github.com/middlewares/whoops/issues", - "source": "https://github.com/middlewares/whoops/tree/v2.0.2" - }, - "time": "2022-01-27T20:31:30+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/http-server-middleware", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-middleware.git", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0", - "psr/http-server-handler": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side middleware", - "keywords": [ - "http", - "http-interop", - "middleware", - "psr", - "psr-15", - "psr-7", - "request", - "response" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-middleware/issues", - "source": "https://github.com/php-fig/http-server-middleware/tree/master" - }, - "time": "2018-10-30T17:12:04+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "doctrine/coding-standard", - "version": "8.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/coding-standard.git", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "slevomat/coding-standard": "^6.4.1", - "squizlabs/php_codesniffer": "^3.5.8" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Steve Müller", - "email": "st.mueller@dzh-online.de" - } - ], - "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", - "keywords": [ - "checks", - "code", - "coding", - "cs", - "doctrine", - "rules", - "sniffer", - "sniffs", - "standard", - "style" - ], - "support": { - "issues": "https://github.com/doctrine/coding-standard/issues", - "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" - }, - "time": "2021-04-03T10:54:55+00:00" - }, - { - "name": "mnapoli/hard-mode", - "version": "0.3.0", - "source": { - "type": "git", - "url": "https://github.com/mnapoli/hard-mode.git", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", - "shasum": "" - }, - "require": { - "doctrine/coding-standard": "^8.0" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Hard mode for PHP", - "support": { - "issues": "https://github.com/mnapoli/hard-mode/issues", - "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" - }, - "time": "2020-10-12T07:54:37+00:00" - }, - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.9", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.26", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/master" - }, - "time": "2020-08-03T20:32:43+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.0" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-24T18:18:00+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^1.2.0" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" - }, - "time": "2021-11-18T09:30:29+00:00" - }, - { - "name": "psalm/phar", - "version": "4.22.0", - "source": { - "type": "git", - "url": "https://github.com/psalm/phar.git", - "reference": "feebed09c9782d9aaa819b794d880c2671ba0e4c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/psalm/phar/zipball/feebed09c9782d9aaa819b794d880c2671ba0e4c", - "reference": "feebed09c9782d9aaa819b794d880c2671ba0e4c", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "vimeo/psalm": "*" - }, - "bin": [ - "psalm.phar" - ], - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer-based Psalm Phar", - "support": { - "issues": "https://github.com/psalm/phar/issues", - "source": "https://github.com/psalm/phar/tree/4.22.0" - }, - "time": "2022-02-27T11:01:37+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "6.4.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpstan/phpstan": "0.12.48", - "phpstan/phpstan-deprecation-rules": "0.12.5", - "phpstan/phpstan-phpunit": "0.12.16", - "phpstan/phpstan-strict-rules": "0.12.5", - "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2020-10-05T12:39:37+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/14-middleware/config/dependencies.php b/implementation/14-middleware/config/dependencies.php deleted file mode 100644 index e84ad53..0000000 --- a/implementation/14-middleware/config/dependencies.php +++ /dev/null @@ -1,42 +0,0 @@ - fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, - Emitter::class => fn (BasicEmitter $e) => $e, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), - RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), -]; diff --git a/implementation/14-middleware/config/middlewares.php b/implementation/14-middleware/config/middlewares.php deleted file mode 100644 index 71dd461..0000000 --- a/implementation/14-middleware/config/middlewares.php +++ /dev/null @@ -1,11 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); - $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -}; diff --git a/implementation/14-middleware/config/settings.php b/implementation/14-middleware/config/settings.php deleted file mode 100644 index b489400..0000000 --- a/implementation/14-middleware/config/settings.php +++ /dev/null @@ -1,11 +0,0 @@ -aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/14-middleware/public/index.php b/implementation/14-middleware/public/index.php deleted file mode 100644 index d93da3a..0000000 --- a/implementation/14-middleware/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render('hello', $data); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/14-middleware/src/Action/Other.php b/implementation/14-middleware/src/Action/Other.php deleted file mode 100644 index 895796e..0000000 --- a/implementation/14-middleware/src/Action/Other.php +++ /dev/null @@ -1,19 +0,0 @@ -getBody(); - - $body->write('This works too!'); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/14-middleware/src/Bootstrap.php b/implementation/14-middleware/src/Bootstrap.php deleted file mode 100644 index 3abc2e5..0000000 --- a/implementation/14-middleware/src/Bootstrap.php +++ /dev/null @@ -1,40 +0,0 @@ -getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); diff --git a/implementation/14-middleware/src/Exception/InternalServerError.php b/implementation/14-middleware/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/implementation/14-middleware/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ -factory::fromGlobals(); - } - - /** - * @param UriInterface|string $uri - * @param array $serverParams - */ - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} diff --git a/implementation/14-middleware/src/Factory/FileSystemSettingsProvider.php b/implementation/14-middleware/src/Factory/FileSystemSettingsProvider.php deleted file mode 100644 index f071078..0000000 --- a/implementation/14-middleware/src/Factory/FileSystemSettingsProvider.php +++ /dev/null @@ -1,22 +0,0 @@ -filePath; - assert($settings instanceof Settings); - return $settings; - } -} diff --git a/implementation/14-middleware/src/Factory/PipelineProvider.php b/implementation/14-middleware/src/Factory/PipelineProvider.php deleted file mode 100644 index 77738f8..0000000 --- a/implementation/14-middleware/src/Factory/PipelineProvider.php +++ /dev/null @@ -1,25 +0,0 @@ -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} diff --git a/implementation/14-middleware/src/Factory/RequestFactory.php b/implementation/14-middleware/src/Factory/RequestFactory.php deleted file mode 100644 index 2b17abc..0000000 --- a/implementation/14-middleware/src/Factory/RequestFactory.php +++ /dev/null @@ -1,11 +0,0 @@ -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn (): Settings => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} diff --git a/implementation/14-middleware/src/Factory/SettingsProvider.php b/implementation/14-middleware/src/Factory/SettingsProvider.php deleted file mode 100644 index ce1c5f0..0000000 --- a/implementation/14-middleware/src/Factory/SettingsProvider.php +++ /dev/null @@ -1,10 +0,0 @@ -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - - echo $response->getBody(); - } -} diff --git a/implementation/14-middleware/src/Http/ContainerPipeline.php b/implementation/14-middleware/src/Http/ContainerPipeline.php deleted file mode 100644 index 816cedd..0000000 --- a/implementation/14-middleware/src/Http/ContainerPipeline.php +++ /dev/null @@ -1,82 +0,0 @@ - $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} diff --git a/implementation/14-middleware/src/Http/Emitter.php b/implementation/14-middleware/src/Http/Emitter.php deleted file mode 100644 index ce4c035..0000000 --- a/implementation/14-middleware/src/Http/Emitter.php +++ /dev/null @@ -1,10 +0,0 @@ -getAttribute($this->routeAttributeName, false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} diff --git a/implementation/14-middleware/src/Http/Pipeline.php b/implementation/14-middleware/src/Http/Pipeline.php deleted file mode 100644 index 1a9dcda..0000000 --- a/implementation/14-middleware/src/Http/Pipeline.php +++ /dev/null @@ -1,11 +0,0 @@ -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} diff --git a/implementation/14-middleware/src/Http/RoutedRequestHandler.php b/implementation/14-middleware/src/Http/RoutedRequestHandler.php deleted file mode 100644 index a7407c9..0000000 --- a/implementation/14-middleware/src/Http/RoutedRequestHandler.php +++ /dev/null @@ -1,10 +0,0 @@ -pipeline->dispatch($request); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} diff --git a/implementation/14-middleware/src/Service/Time/Now.php b/implementation/14-middleware/src/Service/Time/Now.php deleted file mode 100644 index 79224b3..0000000 --- a/implementation/14-middleware/src/Service/Time/Now.php +++ /dev/null @@ -1,10 +0,0 @@ -engine->render($template, $data); - } -} diff --git a/implementation/14-middleware/src/Template/Renderer.php b/implementation/14-middleware/src/Template/Renderer.php deleted file mode 100644 index ff916ed..0000000 --- a/implementation/14-middleware/src/Template/Renderer.php +++ /dev/null @@ -1,11 +0,0 @@ - $data - */ - public function render(string $template, array $data = []): string; -} diff --git a/implementation/14-middleware/templates/hello.html b/implementation/14-middleware/templates/hello.html deleted file mode 100644 index 0e21f2a..0000000 --- a/implementation/14-middleware/templates/hello.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Hello World - - -

Hello {{name}}

-

The time is {{now}}

- - \ No newline at end of file diff --git a/implementation/15-adding-content/.php-cs-fixer.php b/implementation/15-adding-content/.php-cs-fixer.php deleted file mode 100644 index 705a7d7..0000000 --- a/implementation/15-adding-content/.php-cs-fixer.php +++ /dev/null @@ -1,38 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config' - ]) - ); \ No newline at end of file diff --git a/implementation/15-adding-content/.phpcs.xml.dist b/implementation/15-adding-content/.phpcs.xml.dist deleted file mode 100644 index 3b433f6..0000000 --- a/implementation/15-adding-content/.phpcs.xml.dist +++ /dev/null @@ -1,9 +0,0 @@ - - - - - src - config - - - \ No newline at end of file diff --git a/implementation/15-adding-content/cli-config.php b/implementation/15-adding-content/cli-config.php deleted file mode 100644 index fbc6598..0000000 --- a/implementation/15-adding-content/cli-config.php +++ /dev/null @@ -1,13 +0,0 @@ -getContainer(); - -return ConsoleRunner::createHelperSet($container->get(EntityManagerInterface::class)); diff --git a/implementation/15-adding-content/composer.json b/implementation/15-adding-content/composer.json deleted file mode 100644 index 4809539..0000000 --- a/implementation/15-adding-content/composer.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3", - "mustache/mustache": "^2.14", - "psr/http-server-middleware": "^1.0", - "middlewares/trailing-slash": "^2.0", - "middlewares/whoops": "^2.0", - "erusev/parsedown": "^1.7", - "symfony/cache": "^6.0", - "doctrine/orm": "^2.11", - "league/commonmark": "^2.2" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require-dev": { - "phpstan/phpstan": "^1.5", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0", - "squizlabs/php_codesniffer": "^3.6", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1", - "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0" - }, - "config": { - "allow-plugins": { - "phpstan/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "serve": [ - "Composer\\Config::disableProcessTimeout", - "php -S 0.0.0.0:1234 -t public" - ], - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" - } -} diff --git a/implementation/15-adding-content/composer.lock b/implementation/15-adding-content/composer.lock deleted file mode 100644 index 648f2d5..0000000 --- a/implementation/15-adding-content/composer.lock +++ /dev/null @@ -1,4246 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "1e8469bfebe6479a139b946b8aba49de", - "packages": [ - { - "name": "dflydev/dot-access-data", - "version": "v3.0.1", - "source": { - "type": "git", - "url": "https://github.com/dflydev/dflydev-dot-access-data.git", - "reference": "0992cc19268b259a39e86f296da5f0677841f42c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", - "reference": "0992cc19268b259a39e86f296da5f0677841f42c", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.42", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", - "scrutinizer/ocular": "1.6.0", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^3.14" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Dflydev\\DotAccessData\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dragonfly Development Inc.", - "email": "info@dflydev.com", - "homepage": "http://dflydev.com" - }, - { - "name": "Beau Simensen", - "email": "beau@dflydev.com", - "homepage": "http://beausimensen.com" - }, - { - "name": "Carlos Frutos", - "email": "carlos@kiwing.it", - "homepage": "https://github.com/cfrutos" - }, - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com" - } - ], - "description": "Given a deep data structure, access data by dot notation.", - "homepage": "https://github.com/dflydev/dflydev-dot-access-data", - "keywords": [ - "access", - "data", - "dot", - "notation" - ], - "support": { - "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", - "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" - }, - "time": "2021-08-13T13:06:58+00:00" - }, - { - "name": "doctrine/cache", - "version": "2.1.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/cache.git", - "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/331b4d5dbaeab3827976273e9356b3b453c300ce", - "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce", - "shasum": "" - }, - "require": { - "php": "~7.1 || ^8.0" - }, - "conflict": { - "doctrine/common": ">2.2,<2.4" - }, - "require-dev": { - "alcaeus/mongo-php-adapter": "^1.1", - "cache/integration-tests": "dev-master", - "doctrine/coding-standard": "^8.0", - "mongodb/mongodb": "^1.1", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "predis/predis": "~1.0", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^4.4 || ^5.2 || ^6.0@dev", - "symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev" - }, - "suggest": { - "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", - "homepage": "https://www.doctrine-project.org/projects/cache.html", - "keywords": [ - "abstraction", - "apcu", - "cache", - "caching", - "couchdb", - "memcached", - "php", - "redis", - "xcache" - ], - "support": { - "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/2.1.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", - "type": "tidelift" - } - ], - "time": "2021-07-17T14:49:29+00:00" - }, - { - "name": "doctrine/collections", - "version": "1.6.8", - "source": { - "type": "git", - "url": "https://github.com/doctrine/collections.git", - "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/1958a744696c6bb3bb0d28db2611dc11610e78af", - "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af", - "shasum": "" - }, - "require": { - "php": "^7.1.3 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5", - "vimeo/psalm": "^4.2.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", - "homepage": "https://www.doctrine-project.org/projects/collections.html", - "keywords": [ - "array", - "collections", - "iterators", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/1.6.8" - }, - "time": "2021-08-10T18:51:53+00:00" - }, - { - "name": "doctrine/common", - "version": "3.2.2", - "source": { - "type": "git", - "url": "https://github.com/doctrine/common.git", - "reference": "295082d3750987065912816a9d536c2df735f637" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/295082d3750987065912816a9d536c2df735f637", - "reference": "295082d3750987065912816a9d536c2df735f637", - "shasum": "" - }, - "require": { - "doctrine/persistence": "^2.0", - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "^1.4.1", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", - "squizlabs/php_codesniffer": "^3.0", - "symfony/phpunit-bridge": "^4.0.5", - "vimeo/psalm": "^4.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", - "homepage": "https://www.doctrine-project.org/projects/common.html", - "keywords": [ - "common", - "doctrine", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/common/issues", - "source": "https://github.com/doctrine/common/tree/3.2.2" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", - "type": "tidelift" - } - ], - "time": "2022-02-02T09:15:57+00:00" - }, - { - "name": "doctrine/dbal", - "version": "3.3.4", - "source": { - "type": "git", - "url": "https://github.com/doctrine/dbal.git", - "reference": "83f779beaea1893c0bece093ab2104c6d15a7f26" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/83f779beaea1893c0bece093ab2104c6d15a7f26", - "reference": "83f779beaea1893c0bece093ab2104c6d15a7f26", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2", - "doctrine/cache": "^1.11|^2.0", - "doctrine/deprecations": "^0.5.3", - "doctrine/event-manager": "^1.0", - "php": "^7.3 || ^8.0", - "psr/cache": "^1|^2|^3", - "psr/log": "^1|^2|^3" - }, - "require-dev": { - "doctrine/coding-standard": "9.0.0", - "jetbrains/phpstorm-stubs": "2021.1", - "phpstan/phpstan": "1.4.6", - "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "9.5.16", - "psalm/plugin-phpunit": "0.16.1", - "squizlabs/php_codesniffer": "3.6.2", - "symfony/cache": "^5.2|^6.0", - "symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0", - "vimeo/psalm": "4.22.0" - }, - "suggest": { - "symfony/console": "For helpful console commands such as SQL execution and import of files." - }, - "bin": [ - "bin/doctrine-dbal" - ], - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\DBAL\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - } - ], - "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", - "homepage": "https://www.doctrine-project.org/projects/dbal.html", - "keywords": [ - "abstraction", - "database", - "db2", - "dbal", - "mariadb", - "mssql", - "mysql", - "oci8", - "oracle", - "pdo", - "pgsql", - "postgresql", - "queryobject", - "sasql", - "sql", - "sqlite", - "sqlserver", - "sqlsrv" - ], - "support": { - "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.3.4" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", - "type": "tidelift" - } - ], - "time": "2022-03-20T18:37:29+00:00" - }, - { - "name": "doctrine/deprecations", - "version": "v0.5.3", - "source": { - "type": "git", - "url": "https://github.com/doctrine/deprecations.git", - "reference": "9504165960a1f83cc1480e2be1dd0a0478561314" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314", - "reference": "9504165960a1f83cc1480e2be1dd0a0478561314", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^6.0|^7.0|^8.0", - "phpunit/phpunit": "^7.0|^8.0|^9.0", - "psr/log": "^1.0" - }, - "suggest": { - "psr/log": "Allows logging deprecations via PSR-3 logger implementation" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", - "homepage": "https://www.doctrine-project.org/", - "support": { - "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v0.5.3" - }, - "time": "2021-03-21T12:59:47+00:00" - }, - { - "name": "doctrine/event-manager", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/event-manager.git", - "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", - "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/common": "<2.9@dev" - }, - "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpunit/phpunit": "^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/event-manager.html", - "keywords": [ - "event", - "event dispatcher", - "event manager", - "event system", - "events" - ], - "support": { - "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/1.1.x" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", - "type": "tidelift" - } - ], - "time": "2020-05-29T18:28:51+00:00" - }, - { - "name": "doctrine/inflector", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/doctrine/inflector.git", - "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", - "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^8.2", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "vimeo/psalm": "^4.10" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", - "homepage": "https://www.doctrine-project.org/projects/inflector.html", - "keywords": [ - "inflection", - "inflector", - "lowercase", - "manipulation", - "php", - "plural", - "singular", - "strings", - "uppercase", - "words" - ], - "support": { - "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.4" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", - "type": "tidelift" - } - ], - "time": "2021-10-22T20:16:43+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2022-03-03T08:28:38+00:00" - }, - { - "name": "doctrine/lexer", - "version": "1.2.3", - "source": { - "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.11" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", - "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.2.3" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" - } - ], - "time": "2022-02-28T11:07:21+00:00" - }, - { - "name": "doctrine/orm", - "version": "2.11.2", - "source": { - "type": "git", - "url": "https://github.com/doctrine/orm.git", - "reference": "9c351e044478135aec1755e2c0c0493a4b6309db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/9c351e044478135aec1755e2c0c0493a4b6309db", - "reference": "9c351e044478135aec1755e2c0c0493a4b6309db", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2", - "doctrine/cache": "^1.12.1 || ^2.1.1", - "doctrine/collections": "^1.5", - "doctrine/common": "^3.0.3", - "doctrine/dbal": "^2.13.1 || ^3.2", - "doctrine/deprecations": "^0.5.3", - "doctrine/event-manager": "^1.1", - "doctrine/inflector": "^1.4 || ^2.0", - "doctrine/instantiator": "^1.3", - "doctrine/lexer": "^1.0", - "doctrine/persistence": "^2.2", - "ext-ctype": "*", - "php": "^7.1 || ^8.0", - "psr/cache": "^1 || ^2 || ^3", - "symfony/console": "^3.0 || ^4.0 || ^5.0 || ^6.0", - "symfony/polyfill-php72": "^1.23", - "symfony/polyfill-php80": "^1.15" - }, - "conflict": { - "doctrine/annotations": "<1.13 || >= 2.0" - }, - "require-dev": { - "doctrine/annotations": "^1.13", - "doctrine/coding-standard": "^9.0", - "phpbench/phpbench": "^0.16.10 || ^1.0", - "phpstan/phpstan": "1.4.6", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", - "squizlabs/php_codesniffer": "3.6.2", - "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0", - "vimeo/psalm": "4.22.0" - }, - "suggest": { - "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0", - "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" - }, - "bin": [ - "bin/doctrine" - ], - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\ORM\\": "lib/Doctrine/ORM" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "Object-Relational-Mapper for PHP", - "homepage": "https://www.doctrine-project.org/projects/orm.html", - "keywords": [ - "database", - "orm" - ], - "support": { - "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/2.11.2" - }, - "time": "2022-03-09T15:23:58+00:00" - }, - { - "name": "doctrine/persistence", - "version": "2.4.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/persistence.git", - "reference": "092a52b71410ac1795287bb5135704ef07d18dd0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/092a52b71410ac1795287bb5135704ef07d18dd0", - "reference": "092a52b71410ac1795287bb5135704ef07d18dd0", - "shasum": "" - }, - "require": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/collections": "^1.0", - "doctrine/deprecations": "^0.5.3", - "doctrine/event-manager": "^1.0", - "php": "^7.1 || ^8.0", - "psr/cache": "^1.0 || ^2.0 || ^3.0" - }, - "conflict": { - "doctrine/annotations": "<1.0 || >=2.0", - "doctrine/common": "<2.10" - }, - "require-dev": { - "composer/package-versions-deprecated": "^1.11", - "doctrine/annotations": "^1.0", - "doctrine/coding-standard": "^9.0", - "doctrine/common": "^3.0", - "phpstan/phpstan": "1.4.6", - "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.5", - "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "vimeo/psalm": "4.21.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "src/Common", - "Doctrine\\Persistence\\": "src/Persistence" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", - "homepage": "https://doctrine-project.org/projects/persistence.html", - "keywords": [ - "mapper", - "object", - "odm", - "orm", - "persistence" - ], - "support": { - "issues": "https://github.com/doctrine/persistence/issues", - "source": "https://github.com/doctrine/persistence/tree/2.4.1" - }, - "time": "2022-03-22T06:44:40+00:00" - }, - { - "name": "erusev/parsedown", - "version": "1.7.4", - "source": { - "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35" - }, - "type": "library", - "autoload": { - "psr-0": { - "Parsedown": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com" - } - ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org", - "keywords": [ - "markdown", - "parser" - ], - "support": { - "issues": "https://github.com/erusev/parsedown/issues", - "source": "https://github.com/erusev/parsedown/tree/1.7.x" - }, - "time": "2019-12-30T22:54:17+00:00" - }, - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.9.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2022-03-29T20:12:16+00:00" - }, - { - "name": "league/commonmark", - "version": "2.2.3", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/commonmark.git", - "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", - "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "league/config": "^1.1.1", - "php": "^7.4 || ^8.0", - "psr/event-dispatcher": "^1.0", - "symfony/deprecation-contracts": "^2.1 || ^3.0", - "symfony/polyfill-php80": "^1.15" - }, - "require-dev": { - "cebe/markdown": "^1.0", - "commonmark/cmark": "0.30.0", - "commonmark/commonmark.js": "0.30.0", - "composer/package-versions-deprecated": "^1.8", - "erusev/parsedown": "^1.0", - "ext-json": "*", - "github/gfm": "0.29.0", - "michelf/php-markdown": "^1.4", - "phpstan/phpstan": "^0.12.88 || ^1.0.0", - "phpunit/phpunit": "^9.5.5", - "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", - "unleashedtech/php-coding-standard": "^3.1", - "vimeo/psalm": "^4.7.3" - }, - "suggest": { - "symfony/yaml": "v2.3+ required if using the Front Matter extension" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.3-dev" - } - }, - "autoload": { - "psr-4": { - "League\\CommonMark\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com", - "role": "Lead Developer" - } - ], - "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", - "homepage": "https://commonmark.thephpleague.com", - "keywords": [ - "commonmark", - "flavored", - "gfm", - "github", - "github-flavored", - "markdown", - "md", - "parser" - ], - "support": { - "docs": "https://commonmark.thephpleague.com/", - "forum": "https://github.com/thephpleague/commonmark/discussions", - "issues": "https://github.com/thephpleague/commonmark/issues", - "rss": "https://github.com/thephpleague/commonmark/releases.atom", - "source": "https://github.com/thephpleague/commonmark" - }, - "funding": [ - { - "url": "https://www.colinodell.com/sponsor", - "type": "custom" - }, - { - "url": "https://www.paypal.me/colinpodell/10.00", - "type": "custom" - }, - { - "url": "https://github.com/colinodell", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/commonmark", - "type": "tidelift" - } - ], - "time": "2022-02-26T21:24:45+00:00" - }, - { - "name": "league/config", - "version": "v1.1.1", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/config.git", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "shasum": "" - }, - "require": { - "dflydev/dot-access-data": "^3.0.1", - "nette/schema": "^1.2", - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.90", - "phpunit/phpunit": "^9.5.5", - "scrutinizer/ocular": "^1.8.1", - "unleashedtech/php-coding-standard": "^3.1", - "vimeo/psalm": "^4.7.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.2-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Config\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com", - "role": "Lead Developer" - } - ], - "description": "Define configuration arrays with strict schemas and access values with dot notation", - "homepage": "https://config.thephpleague.com", - "keywords": [ - "array", - "config", - "configuration", - "dot", - "dot-access", - "nested", - "schema" - ], - "support": { - "docs": "https://config.thephpleague.com/", - "issues": "https://github.com/thephpleague/config/issues", - "rss": "https://github.com/thephpleague/config/releases.atom", - "source": "https://github.com/thephpleague/config" - }, - "funding": [ - { - "url": "https://www.colinodell.com/sponsor", - "type": "custom" - }, - { - "url": "https://www.paypal.me/colinpodell/10.00", - "type": "custom" - }, - { - "url": "https://github.com/colinodell", - "type": "github" - } - ], - "time": "2021-08-14T12:15:32+00:00" - }, - { - "name": "middlewares/trailing-slash", - "version": "v2.0.1", - "source": { - "type": "git", - "url": "https://github.com/middlewares/trailing-slash.git", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", - "shasum": "" - }, - "require": { - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to normalize the trailing slash of the uri path", - "homepage": "https://github.com/middlewares/trailing-slash", - "keywords": [ - "http", - "middleware", - "normalize", - "path", - "psr-15", - "psr-7", - "slash" - ], - "support": { - "issues": "https://github.com/middlewares/trailing-slash/issues", - "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" - }, - "time": "2020-12-02T00:06:55+00:00" - }, - { - "name": "middlewares/utils", - "version": "v3.3.0", - "source": { - "type": "git", - "url": "https://github.com/middlewares/utils.git", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^v2.16", - "guzzlehttp/psr7": "^2.0", - "laminas/laminas-diactoros": "^2.4", - "nyholm/psr7": "^1.0", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "slim/psr7": "^1.4", - "squizlabs/php_codesniffer": "^3.5", - "sunrise/http-message": "^1.0", - "sunrise/http-server-request": "^1.0", - "sunrise/stream": "^1.0.15", - "sunrise/uri": "^1.0.15" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\Utils\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Common utils for PSR-15 middleware packages", - "homepage": "https://github.com/middlewares/utils", - "keywords": [ - "PSR-11", - "http", - "middleware", - "psr-15", - "psr-17", - "psr-7" - ], - "support": { - "issues": "https://github.com/middlewares/utils/issues", - "source": "https://github.com/middlewares/utils/tree/v3.3.0" - }, - "time": "2021-07-04T17:56:23+00:00" - }, - { - "name": "middlewares/whoops", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/middlewares/whoops.git", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", - "shasum": "" - }, - "require": { - "filp/whoops": "^2.5", - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "eloquent/phony-phpunit": "^5.0 || ^7.0", - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to use Whoops as error handler", - "homepage": "https://github.com/middlewares/whoops", - "keywords": [ - "error", - "http", - "middleware", - "psr-15", - "psr-7", - "server", - "whoops" - ], - "support": { - "issues": "https://github.com/middlewares/whoops/issues", - "source": "https://github.com/middlewares/whoops/tree/v2.0.2" - }, - "time": "2022-01-27T20:31:30+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nette/schema", - "version": "v1.2.2", - "source": { - "type": "git", - "url": "https://github.com/nette/schema.git", - "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", - "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", - "shasum": "" - }, - "require": { - "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": ">=7.1 <8.2" - }, - "require-dev": { - "nette/tester": "^2.3 || ^2.4", - "phpstan/phpstan-nette": "^0.12", - "tracy/tracy": "^2.7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "📐 Nette Schema: validating data structures against a given Schema.", - "homepage": "https://nette.org", - "keywords": [ - "config", - "nette" - ], - "support": { - "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.2" - }, - "time": "2021-10-15T11:40:02+00:00" - }, - { - "name": "nette/utils", - "version": "v3.2.7", - "source": { - "type": "git", - "url": "https://github.com/nette/utils.git", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", - "shasum": "" - }, - "require": { - "php": ">=7.2 <8.2" - }, - "conflict": { - "nette/di": "<3.0.6" - }, - "require-dev": { - "nette/tester": "~2.0", - "phpstan/phpstan": "^1.0", - "tracy/tracy": "^2.3" - }, - "suggest": { - "ext-gd": "to use Image", - "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", - "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", - "ext-json": "to use Nette\\Utils\\Json", - "ext-mbstring": "to use Strings::lower() etc...", - "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", - "ext-xml": "to use Strings::length() etc. when mbstring is not available" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", - "homepage": "https://nette.org", - "keywords": [ - "array", - "core", - "datetime", - "images", - "json", - "nette", - "paginator", - "password", - "slugify", - "string", - "unicode", - "utf-8", - "utility", - "validation" - ], - "support": { - "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.2.7" - }, - "time": "2022-01-24T11:29:14+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/cache", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" - }, - "time": "2021-02-03T23:26:27+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/http-server-middleware", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-middleware.git", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0", - "psr/http-server-handler": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side middleware", - "keywords": [ - "http", - "http-interop", - "middleware", - "psr", - "psr-15", - "psr-7", - "request", - "response" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-middleware/issues", - "source": "https://github.com/php-fig/http-server-middleware/tree/master" - }, - "time": "2018-10-30T17:12:04+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - }, - { - "name": "symfony/cache", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache.git", - "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", - "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "psr/cache": "^2.0|^3.0", - "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^1.1.7|^2|^3", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/var-exporter": "^5.4|^6.0" - }, - "conflict": { - "doctrine/dbal": "<2.13.1", - "symfony/dependency-injection": "<5.4", - "symfony/http-kernel": "<5.4", - "symfony/var-dumper": "<5.4" - }, - "provide": { - "psr/cache-implementation": "2.0|3.0", - "psr/simple-cache-implementation": "1.0|2.0|3.0", - "symfony/cache-implementation": "1.1|2.0|3.0" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/dbal": "^2.13.1|^3.0", - "predis/predis": "^1.1", - "psr/simple-cache": "^1.0|^2.0|^3.0", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/filesystem": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/messenger": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Cache\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", - "homepage": "https://symfony.com", - "keywords": [ - "caching", - "psr6" - ], - "support": { - "source": "https://github.com/symfony/cache/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "symfony/cache-contracts", - "version": "v3.0.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache-contracts.git", - "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/2f7463f156cf9c665d9317e21a809c3bbff5754e", - "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "psr/cache": "^3.0" - }, - "suggest": { - "symfony/cache-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Cache\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to caching", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.0.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-08-17T15:35:52+00:00" - }, - { - "name": "symfony/console", - "version": "v6.0.5", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "3bebf4108b9e07492a2a4057d207aa5a77d146b1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/3bebf4108b9e07492a2a4057d207aa5a77d146b1", - "reference": "3bebf4108b9e07492a2a4057d207aa5a77d146b1", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.4|^6.0" - }, - "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/lock": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command line", - "console", - "terminal" - ], - "support": { - "source": "https://github.com/symfony/console/tree/v6.0.5" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-02-25T10:48:52+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v2.5.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8", - "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-07-12T14:48:14+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "30885182c981ab175d4d034db0f6f469898070ab" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", - "reference": "30885182c981ab175d4d034db0f6f469898070ab", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-10-20T20:35:02+00:00" - }, - { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "81b86b50cf841a64252b439e738e97f4a34e2783" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", - "reference": "81b86b50cf841a64252b439e738e97f4a34e2783", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-23T21:10:46+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-02-19T12:13:01+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/polyfill-php72", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", - "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-05-27T09:17:38+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-04T08:16:47+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v2.5.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", - "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "suggest": { - "symfony/service-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-04T16:48:04+00:00" - }, - { - "name": "symfony/string", - "version": "v6.0.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/522144f0c4c004c80d56fa47e40e17028e2eefc2", - "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/translation-contracts": "<2.0" - }, - "require-dev": { - "symfony/error-handler": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/translation-contracts": "^2.0|^3.0", - "symfony/var-exporter": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "files": [ - "Resources/functions.php" - ], - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "source": "https://github.com/symfony/string/tree/v6.0.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:55:41+00:00" - }, - { - "name": "symfony/var-exporter", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-exporter.git", - "reference": "130229a482abf17635a685590958894dfb4b4360" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/130229a482abf17635a685590958894dfb4b4360", - "reference": "130229a482abf17635a685590958894dfb4b4360", - "shasum": "" - }, - "require": { - "php": ">=8.0.2" - }, - "require-dev": { - "symfony/var-dumper": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\VarExporter\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows exporting any serializable PHP data structure to plain PHP code", - "homepage": "https://symfony.com", - "keywords": [ - "clone", - "construct", - "export", - "hydrate", - "instantiate", - "serialize" - ], - "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "doctrine/coding-standard", - "version": "8.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/coding-standard.git", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "slevomat/coding-standard": "^6.4.1", - "squizlabs/php_codesniffer": "^3.5.8" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Steve Müller", - "email": "st.mueller@dzh-online.de" - } - ], - "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", - "keywords": [ - "checks", - "code", - "coding", - "cs", - "doctrine", - "rules", - "sniffer", - "sniffs", - "standard", - "style" - ], - "support": { - "issues": "https://github.com/doctrine/coding-standard/issues", - "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" - }, - "time": "2021-04-03T10:54:55+00:00" - }, - { - "name": "mnapoli/hard-mode", - "version": "0.3.0", - "source": { - "type": "git", - "url": "https://github.com/mnapoli/hard-mode.git", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", - "shasum": "" - }, - "require": { - "doctrine/coding-standard": "^8.0" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Hard mode for PHP", - "support": { - "issues": "https://github.com/mnapoli/hard-mode/issues", - "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" - }, - "time": "2020-10-12T07:54:37+00:00" - }, - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.9", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.26", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/master" - }, - "time": "2020-08-03T20:32:43+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.5.3", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/39953ac1452a8843702ee41a35b4861d3e8207a7", - "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.3" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-30T21:55:08+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^1.2.0" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" - }, - "time": "2021-11-18T09:30:29+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "6.4.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpstan/phpstan": "0.12.48", - "phpstan/phpstan-deprecation-rules": "0.12.5", - "phpstan/phpstan-phpunit": "0.12.16", - "phpstan/phpstan-strict-rules": "0.12.5", - "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2020-10-05T12:39:37+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.3.0" -} diff --git a/implementation/15-adding-content/config/dependencies.php b/implementation/15-adding-content/config/dependencies.php deleted file mode 100644 index e2a3925..0000000 --- a/implementation/15-adding-content/config/dependencies.php +++ /dev/null @@ -1,60 +0,0 @@ - fn (SystemClockNow $n) => $n, - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, - Emitter::class => fn (BasicEmitter $e) => $e, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, - CacheInterface::class => fn (FilesystemAdapter $a) => $a, - MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, - MarkdownParser::class => fn (ParsedownParser $p) => $p, - - // Factories - ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), - Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - MarkdownPageFilesystem::class => fn (Settings $s) => new MarkdownPageFilesystem($s->pagesPath), - CachedMarkdownPageRepo::class => fn (CacheInterface $c, MarkdownPageFilesystem $r, Settings $s) => new CachedMarkdownPageRepo($c, $r, $s), - EntityManagerInterface::class => fn (DoctrineEm $f) => $f->create(), -]; diff --git a/implementation/15-adding-content/config/middlewares.php b/implementation/15-adding-content/config/middlewares.php deleted file mode 100644 index 71dd461..0000000 --- a/implementation/15-adding-content/config/middlewares.php +++ /dev/null @@ -1,11 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/page', [Page::class, 'list']); - $r->addRoute('GET', '/page/{page}', [Page::class, 'show']); - $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); - $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -}; diff --git a/implementation/15-adding-content/config/settings.php b/implementation/15-adding-content/config/settings.php deleted file mode 100644 index 5c58216..0000000 --- a/implementation/15-adding-content/config/settings.php +++ /dev/null @@ -1,23 +0,0 @@ - 'pdo_sqlite', - 'user' => '', - 'password' => '', - 'path' => __DIR__ . '/../data/db.sqlite', - ], - doctrine: [ - 'devMode' => true, - 'metadataDirs' => [__DIR__ . '/../src/Model/'], - 'cacheDir' => __DIR__ . '/../data/cache/', - ], -); diff --git a/implementation/15-adding-content/data/pages/01-front-controller.md b/implementation/15-adding-content/data/pages/01-front-controller.md deleted file mode 100644 index 87a12ad..0000000 --- a/implementation/15-adding-content/data/pages/01-front-controller.md +++ /dev/null @@ -1,53 +0,0 @@ -[next >>](02-composer.md) - -### Front Controller - -A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. - -To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. - -A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. - -The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. - -But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. - -So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. - -Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: - -```php ->](02-composer.md) diff --git a/implementation/15-adding-content/data/pages/02-composer.md b/implementation/15-adding-content/data/pages/02-composer.md deleted file mode 100644 index a25a4a8..0000000 --- a/implementation/15-adding-content/data/pages/02-composer.md +++ /dev/null @@ -1,75 +0,0 @@ -[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) - -### Composer - -[Composer](https://getcomposer.org/) is a dependency manager for PHP. - -Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do -something. With Composer, you can install third-party libraries for your application. - -If you don't have Composer installed already, head over to the website and install it. You can find Composer packages -for your project on [Packagist](https://packagist.org/). - -Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will -be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. - -Add the following content to the file: - -```json -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubiana", - "email": "lubiana@hannover.ccc.de" - } - ] -} -``` - -In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use -whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. -Just replace it with your namespace in your own code. - -I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. - -As the Bootstrap.php file is placed in that directory we should -add the namespace to the File as well. Here is my current Bootstrap.php -as a reference: - -```php ->](03-error-handler.md) diff --git a/implementation/15-adding-content/data/pages/03-error-handler.md b/implementation/15-adding-content/data/pages/03-error-handler.md deleted file mode 100644 index 60465d0..0000000 --- a/implementation/15-adding-content/data/pages/03-error-handler.md +++ /dev/null @@ -1,79 +0,0 @@ -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) - -### Error Handler - -An error handler allows you to customize what happens if your code results in an error. - -A nice error page with a lot of information for debugging goes a long way during development. So the first package -for your application will take care of that. - -I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. -If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, -you have total control over your project. - -An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) - -To install a new package, open up your `composer.json` and add the package to the require part. It should now look -like this: - -```php -"require": { - "php": ">=8.1.0", - "filp/whoops": "^2.14" -}, -``` - -Now run `composer update` in your console and it will be installed. - -Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, -i that case composer automatically installs the package and updates your composer.json-file. - -But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, -ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you -only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. - -**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message -can help someone to gain access to your system. Always show a user friendly error page instead and send an email to -yourself, write to a log or something similar. So only you can see the errors in the production environment. - -For development that does not make sense though -- you want a nice error page. The solution is to have an environment -switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in -case no environment has been set. - -Then after the error handler registration, throw an `Exception` to test if everything is working correctly. -Your `Bootstrap.php` should now look similar to this: - -```php -pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (\Throwable $e) { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -throw new \Exception("Ooooopsie"); - -``` - -You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until -you get it working. Now would also be a good time for another commit. - - -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/implementation/15-adding-content/data/pages/04-development-helpers.md b/implementation/15-adding-content/data/pages/04-development-helpers.md deleted file mode 100644 index 74f913c..0000000 --- a/implementation/15-adding-content/data/pages/04-development-helpers.md +++ /dev/null @@ -1,260 +0,0 @@ -[<< previous](03-error-handler.md) | [next >>](05-http.md) - -### Development Helpers - -I have added some more helpers to my composer.json that help me with development. As these are scripts and programms -used only for development they should not be used in a production environment. Composer has a specific sections in its -file called "dev-dependencies", everything that is required in this section does not get installen in production. - -Let's install our dev-helpers and i will explain them one by one: -`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` - -#### Static Code Analysis with phpstan - -Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or -create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements -that are not (or not always) available, if-statements that always are true and a lot of other stuff. - -A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. - -```php -/** - * @param \DateTime $date - * @return void - */ -function printDate($date) { - $date->format('Y-m-d H:i:s'); -} - -printDate('now'); -``` -if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` - -It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has -no use, and secondly, that we are calling the function with a string instead of a datetime object. - -```shell -1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - ------ --------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ --------------------------------------------------------------------------------------------- -30 Call to method DateTime::format() on a separate line has no effect. -33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. - ------ --------------------------------------------------------------------------------------------- -``` - -The second error is something that "declare strict-types" already catches for us, but the first error is something that -we usually would not discover easily without speccially looking for this errortype. - -We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and -path everytime we want to check our code for errors: - -```yaml -parameters: - level: max - paths: - - src -``` -now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project - -With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us -some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. - -```shell -composer require --dev phpstan/extension-installer -composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules -``` - -During the first install you need to allow the extension installer to actually install the extension. The second command -installs some more strict rulesets and activates them in phpstan. - -If we now rerun phpstan it already tells us about some errors we have made: - -``` - ------ ----------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ ----------------------------------------------------------------------------------------------- -10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider - using long ternary. -25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: - http://bit.ly/subtypeexception -26 Unreachable statement - code above always terminates. - ------ ----------------------------------------------------------------------------------------------- -``` - -The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove -that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific -problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working -on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages -everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we -are adding to our code. - -In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the -phpstan.neon file and run phpstan with the -'--generate-baseline' option: - -```yaml -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` -```shell -[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline -Note: Using configuration file /home/vagrant/app/phpstan.neon. - 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - - - [OK] Baseline generated with 1 error. - - -``` - -you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) - -#### PHP-CS-Fixer - -Another great tool is the php-cs-fixer, which just applies a specific style to your code. - -when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current -directory. - -You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) - -personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup -a configuration file that i use in all my projects .php-cs-fixer.php: - -```php -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - ]) - ); -``` - -#### PHP Codesniffer - -The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra -rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. - -it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. - -Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have -guessed we are adding some extra rules. - -Lets install the ruleset with composer -```shell -composer require --dev mnapoli/hard-mode -``` - -and add a configuration file to actually use it '.phpcs.xml.dist' -```xml - - - - - src - - - -``` - -running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. - -``` -[vagrant@archlinux app]$ ./vendor/bin/phpcs - -FILE: src/Bootstrap.php ----------------------------------------------------------------------------------------------------- -FOUND 4 ERRORS AFFECTING 4 LINES ----------------------------------------------------------------------------------------------------- - 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. - 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead ----------------------------------------------------------------------------------------------------- -PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY ----------------------------------------------------------------------------------------------------- - -Time: 639ms; Memory: 10MB -``` - -You can then use `./vendor/bin/phpcbf` to try to fix them - - -#### Symfony Var-Dumper - -another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small -functions. - -dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects -or arrays. - -dd() on the other hand is a function that dumps its parameters and then exits the php-script. - -you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. - -#### Composer scripts - -now we have a few commands that are available on the command line. i personally do not like to type complex commands -with lots of parameters by hand all the time, so i added a few lines to my composer.json: - -```json -"scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" -}, -``` - -that way i can just type "composer" followed by the command name in the root of my project. if i want to start the -php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the -time. - -You could also configure PhpStorm to automatically run these commands in the background and highlight the violations -directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my -flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. - -My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before -commiting and pushing the code. - -[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/implementation/15-adding-content/data/pages/05-http.md b/implementation/15-adding-content/data/pages/05-http.md deleted file mode 100644 index 6166214..0000000 --- a/implementation/15-adding-content/data/pages/05-http.md +++ /dev/null @@ -1,124 +0,0 @@ -[<< previous](04-development-helpers.md) | [next >>](06-router.md) - -### HTTP - -PHP already has a few things built in to make working with HTTP easier. For example there are the -[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. - -These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, -if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, -then you will want a class with a nice object-oriented interface that you can use in your application instead. - -Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The -standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php -projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. - -As this is a widely adopted standard there are already several implementations available for us to use. I will choose -the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. - -Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a -[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. - -Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use -that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding -the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). - - -to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit -enter - -Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): - -```php -$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); -$response = new \Laminas\Diactoros\Response; -$response->getBody()->write('Hello World! '); -$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); -``` - -This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. - -In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the -write()-Method on that object. - - -To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: - -```php -echo $response->getBody(); -``` - -This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only -stores data. - -You can play around with the other methods of the Request object and take a look at its content with the dd() function. - -```php -dd($response) -``` - -Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot -be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which -creates a copy of the request object with the changed property and returns that clone: - -```php -$response = $response->withStatus(200); -$response = $response->withAddedHeader('Content-type', 'application/json'); -``` - -If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been -defined this way. - -But if you have been keeping attention you might argue that the following line should not work if the request object is -immutable. - -```php -$response->getBody()->write('Hello World!'); -``` - -The response-body implements a stream interface which is immutable for some reasons that are described in the -[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be -aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in -the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. -I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content -to a response object. - -```php -$body = $response->getBody(); -$body->write('Hello World!'); -$response = $response->withBody($body); -``` - -Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our -output-logic a little bit more. Replace the line that echos the response-body with the following: - -```php -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); -``` - -This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a -webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. - -Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. - -Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter - - -[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/implementation/15-adding-content/data/pages/06-router.md b/implementation/15-adding-content/data/pages/06-router.md deleted file mode 100644 index 6c39ae5..0000000 --- a/implementation/15-adding-content/data/pages/06-router.md +++ /dev/null @@ -1,101 +0,0 @@ -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) - -### Router - -A router dispatches to different handlers depending on rules that you have set up. - -With your current setup it does not matter what URL is used to access the application, it will always result in the same -response. So let's fix that now. - -I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own -favorite package. - -Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) - -By now you know how to install Composer packages, so I will leave that to you. - -Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. - -```php -$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -switch ($routeInfo[0]) { - case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: - $response = (new \Laminas\Diactoros\Response)->withStatus(405); - $response->getBody()->write('Method not allowed'); - $response = $response->withStatus(405); - break; - case \FastRoute\Dispatcher::FOUND: - $handler = $routeInfo[1]; - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - /** @var \Psr\Http\Message\ResponseInterface $response */ - $response = call_user_func($handler, $request); - break; - case \FastRoute\Dispatcher::NOT_FOUND: - default: - $response = (new \Laminas\Diactoros\Response)->withStatus(404); - $response->getBody()->write('Not Found!'); - break; -} -``` - -In the first part of the code, you are registering the available routes for your application. In the second part, the -dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, -we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. -If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. - -This setup might work for really small applications, but once you start adding a few routes your bootstrap file will -quickly get cluttered. So let's move them out into a separate file. - -Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; - -```php -addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}; -``` - -Now let's rewrite the route dispatcher part to use the `Routes.php` file. - -```php -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); -``` - -This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. - -Of course we now need to add the 'config' folder to the configuration files of our -devhelpers so that they can scan that directory as well. - -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/implementation/15-adding-content/data/pages/07-dispatching-to-a-class.md b/implementation/15-adding-content/data/pages/07-dispatching-to-a-class.md deleted file mode 100644 index 0c961a4..0000000 --- a/implementation/15-adding-content/data/pages/07-dispatching-to-a-class.md +++ /dev/null @@ -1,137 +0,0 @@ -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) - -### Dispatching to a Class - -In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). -MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to -learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) -and the followup posts. - -So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). - -We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other -common names are 'Controllers' or 'Actions'. - -Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. -In there, create a `Hello.php` file. - -```php -getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - } -} -``` - -You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) -that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is -fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement -it in some other parts of our application as well. In order to use that Interface we have to require it with composer: -'composer require psr/http-server-handler'. - -The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. -At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. - -Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: - -```php -return function(\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); - $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); -}; -``` - -Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared -the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing -the response to the one we defined for the second route. - -To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: - -```php -case \FastRoute\Dispatcher::FOUND: - $handler = new $routeInfo[1]; - if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { - throw new \Exception('Invalid Requesthandler'); - } - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - assert($response instanceof \Psr\Http\Message\ResponseInterface) - break; -``` - -So instead of just calling a method you are now instantiating an object and then calling the method on it. - -Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. - -And of course don't forget to commit your changes. - -Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still -generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for -example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still -have our whoopsie error-handler but i like to be more explicit in my control flow. - -In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new -Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and -InternalServerError. All three should extend phps Base Exception class. - -Here is my NotFound.php for example. - -```php - $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - break; - case Dispatcher::METHOD_NOT_ALLOWED: - throw new MethodNotAllowed; - - case Dispatcher::NOT_FOUND: - default: - throw new NotFound; - } -} catch (MethodNotAllowed) { - $response = (new Response)->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = (new Response)->withStatus(404); - $response->getBody()->write('Not Found'); -} catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); -} -``` - -Check if our code still works, try to trigger some errors, run phpstan and the fix command -and don't forget to commit your changes. - -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/implementation/15-adding-content/data/pages/08-inversion-of-control.md b/implementation/15-adding-content/data/pages/08-inversion-of-control.md deleted file mode 100644 index 21f4f23..0000000 --- a/implementation/15-adding-content/data/pages/08-inversion-of-control.md +++ /dev/null @@ -1,54 +0,0 @@ -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) - -### Inversion of Control - -In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we -want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then -we would need to edit every one of our request handlers to call a different constructor of the class. - -The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that -instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done -with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). - -If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is -implemented, it will make sense. - -Change your `Hello` action to the following: - -```php -getAttribute('name', 'Stranger'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: - -```php -$handler = new $className($response); -``` - -Of course we need to also update all the other handlers. - -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/implementation/15-adding-content/data/pages/09-dependency-injector.md b/implementation/15-adding-content/data/pages/09-dependency-injector.md deleted file mode 100644 index 7f7c6a2..0000000 --- a/implementation/15-adding-content/data/pages/09-dependency-injector.md +++ /dev/null @@ -1,213 +0,0 @@ -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) - -### Dependency Injector - -A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when -the class is instantiated. - -Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work -with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look -for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). - -I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) -out of the box. - -After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: - -```php -addDefinitions([ - \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), - \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), -]); - -return $builder->build(); -``` - -In this file we create a containerbuilder, add some definitions to it and return the container. -As the container supports autowiring we only need to define services where we want to use a specific implementation of -an interface. - -In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to -tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second -exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. - -Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), -as we will use that extensively. - -Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally -to create our Handler-Object - -```php -$container = require __DIR__ . '/../config/dependencies.php'; -assert($container instanceof \Psr\Container\ContainerInterface); - -$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); -assert($request instanceof \Psr\Http\Message\ServerRequestInterface); -``` - -The other part that has to be changed is the dispatching of the route. Before you had the following code: - -```php -$className = $routeInfo[1]; -$handler = new $className($response); -assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Change that to the following: - -```php -/** @var RequestHandlerInterface $handler */ -$className = $routeInfo[1]; -$handler = $container->get($className); -assert($handler instanceof RequestHandlerInterface); -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Make sure to use the container fetch the response object in the catch blocks as well: - -```php -} catch (MethodNotAllowed) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(404); - $response->getBody()->write('Not Found'); -} -``` - -Now all your controller constructor dependencies will be automatically resolved with PHP-DI. - -We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons -in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call -`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we -want to work with a different date in a test. - -Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation -that creates us a DateTimeImmutable object with the current date and time. - -src/Service/Time/Now.php: -```php -namespace Lubian\NoFramework\Service\Time; - -interface Now -{ - public function __invoke(): \DateTimeImmutable; -} -``` -src/Service/Time/SystemClockNow.php: -```php -namespace Lubian\NoFramework\Service\Time; - -final class SystemClockNow implements Now -{ - - public function __invoke(): \DateTimeImmutable - { - return new \DateTimeImmutable; - } -} -``` -If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and -update the handle-method to use the new class property: - -```php -getAttribute('name', 'Stranger'); - $nowAsString = ($this->now)()->format('H:i:s'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowAsString); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI -automatically figures out what classes are requested in the constructor and tries to create the objects needed. - -But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred -S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: - -```php - public function __construct( - private ResponseInterface $response, - private Now $now, - ) -``` - -When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation -should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: - -```php -\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), -``` - -we could also use the PHP-DI create method to delegate the object creation to the container implementation: -```php -\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), -``` - -this way the container can try to resolve any dependencies that the class might have internally, but prefer the other -method because we are not depending on this specific dependency injection implementation. - -Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are -requesting the Hello action. - -If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As -we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way -we wont get any warnings for this particular issue, but for any other issues we add to our code. - -Update the phpstan.neon file to include a "baseline" file: - -``` -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` - -if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and -ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just -'baseline' - -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/implementation/15-adding-content/data/pages/10-invoker.md b/implementation/15-adding-content/data/pages/10-invoker.md deleted file mode 100644 index 3033fae..0000000 --- a/implementation/15-adding-content/data/pages/10-invoker.md +++ /dev/null @@ -1,102 +0,0 @@ -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) - -### Invoker - -Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the -one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long -with some services and not the whole Requestobject with all its various properties. - -If we take our Hello action for example we only need a response object, the time service and the 'name' information from -the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named -the class hello and it would be redundant to also call the the method hello. So an updated version of that class could -look like this: - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - string $name = 'Stranger', - ): ResponseInterface - { - $body = $this->response->getBody(); - $nowString = $now->get()->format('H:i:s'); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowString); - return $response - ->withBody($body) - ->withStatus(200); - } -} -``` - -It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short -closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the -rootpath of our application yet. - -```php -$r->addRoute('GET', '/hello[/{name}]', Hello::class); -$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); -$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -``` - -In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the -route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that -class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what -arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router -if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple -callable we would need to manually use reflection as well to resolve all the arguments. - -But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) -which handles all of that for us. - -After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo -switch section in the Bootstrap.php file to use the container->call() method: - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2]; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$response = $container->call($handler, $args); -``` - -Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. - -But by now you should know that I do not like to depend on specific implementations and the call method is not defined in -the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container -or any other implementation. - -Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific -container implementation so we could use it with any container that implements the ContainerInterface. And best of all -the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that -we could implement if we ever want to write our own implementation or we could write an adapter that uses a different -class that solves the same problem. - -But for now we are using the solution provided by PHP-DI. -So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2] ?? []; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$invoker = $container->get(InvokerInterface::class); -assert($invoker instanceof InvokerInterface); -$response = $invoker->call($handler, $args); -assert($response instanceof ResponseInterface); -``` - -Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) -by php, and even some more. - -But let us move on to something more fun and add some templating functionality to our application as we are trying to build -a website in the end. - -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/implementation/15-adding-content/data/pages/11-templating.md b/implementation/15-adding-content/data/pages/11-templating.md deleted file mode 100644 index 3759664..0000000 --- a/implementation/15-adding-content/data/pages/11-templating.md +++ /dev/null @@ -1,240 +0,0 @@ -[<< previous](10-invoker.md) | [next >>](12-configuration.md) - -### Templating - -A template engine is not necessary with PHP because the language itself can take care of that. But it can make things -like escaping values easier. They also make it easier to draw a clear line between your application logic and the -template files which should only put your variables into the HTML code. - -A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please -also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. -Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. - -For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install -that package before you continue (`composer require mustache/mustache`). - -Another well known alternative would be [Twig](http://twig.sensiolabs.org/). - -Now please go and have a look at the source code of the -[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class -does not implement an interface. - -You could just type hint against the concrete class. But the problem with this approach is that you create tight -coupling. - -In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the -implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want -to add functionality to the engine. You can't do that without going back and changing all your code that is tightly -coupled. - -What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need -another implementation, you just implement that interface in your new class and inject the new class instead. - -Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). -This sounds a lot more complicated than it is, so just follow along. - -First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). -This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. -A class can implement multiple interfaces if necessary. - -So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a -new folder in your `src/` folder with the name `Template` where you can put all the template related things. - -In there create a new interface `Renderer.php` that looks like this: - -```php - $data - * @return string - */ - public function render(string $template, array $data = []) : string; -} -``` - -Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file -`MustacheRenderer.php` with the following content: - -```php -engine->render($template, $data); - } -} -``` - -As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple -and only fulfills the interface. - -Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know -which implementation he has to inject when you hint for the interface. Add this line: - -```php -[ - ... - \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) - ->constructor(new Mustache_Engine), -] -``` - -Now update the Hello.php class to require an implementation of our renderer interface -and use that to render a string using mustache syntax. - - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - Renderer $renderer, - string $name = 'Stranger', - ): ResponseInterface { - $body = $response->getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render( - 'Hello {{name}}, the time is {{now}}!', - $data, - ); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} -``` - -Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. -But what we want is template files, so let's go back and change that. - -To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the -`dependencies.php` file and add the following code: - -```php -[ - ... - Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), - Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), -] -``` - -We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. -Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have -to rename all the template files. - -To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the -dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader -in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object -we can then use it in the factory. - -In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: - -``` -

Hello World

-Hello {{ name }} -``` - -Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` - -Navigate to the hello page in your browser to make sure everything works. - -One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies -file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration -values. - -Lets create a 'Settings' class in our './src' Folder: - -```php -addDefinitions([ - Settings::class => fn () => require __DIR__ '/settings.php', - ResponseInterface::class => create(Response::class), - ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), - Renderer::class => fn (ME $me) => new Mustache($me), - MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), -]); - -return $builder->build(); -``` - - - -And as always, don't forget to commit your changes. - - -[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/implementation/15-adding-content/data/pages/12-configuration.md b/implementation/15-adding-content/data/pages/12-configuration.md deleted file mode 100644 index a44dfd5..0000000 --- a/implementation/15-adding-content/data/pages/12-configuration.md +++ /dev/null @@ -1,201 +0,0 @@ -[<< previous](11-templating.md) | [next >>](13-refactoring.md) - -### Configuration - -In the last chapter we added some more definitions to our dependencies.php in that definitions -we needed to pass quite a few configuration settings and filesystem strings to the constructors -of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. - -As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. - -As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. - -Lets create a 'Settings' class in our './src' Folder: - -```php -filePath; - } -} -``` - -If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files -and craft a settings object from them. - -As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another -factory for our Container because everyone should have a Friend :) - -```php -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn () => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} -``` - -For this to work we need to change our dependencies.php file to just return the array of definitions: -And here we can instantly use the Settings object to create our template engine. - -```php - fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), -]; -``` - -Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: -require __DIR__ . '/../vendor/autoload.php'; - -```php -... -error_reporting(E_ALL); - -$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); -$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); -... -``` - -Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. - -[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/implementation/15-adding-content/data/pages/13-refactoring.md b/implementation/15-adding-content/data/pages/13-refactoring.md deleted file mode 100644 index 067e168..0000000 --- a/implementation/15-adding-content/data/pages/13-refactoring.md +++ /dev/null @@ -1,377 +0,0 @@ -[<< previous](12-configuration.md) | [next >>](14-middleware.md) - -### Refactoring - -By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no -reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. -After all the bootstrap file should just set up the classes needed for the handling logic and execute them. - -At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. -As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the -method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non -static and extracted an interface. - -'./src/Http/Emitter.php' -```php -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - echo $response->getBody(); - } -} -``` -After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following -code in the Bootstrap.php to emit the response: - -```php -/** @var Emitter $emitter */ -$emitter = $container->get(Emitter::class); -$emitter->emit($response); -``` - -If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily -write an adapter that implements your emitter interface and wraps that more advanced emitter - -Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and -calling the routerhandler that in the passes the request to a function and gets the response. - -For this to steps to be seperated we are going to create two more classes: -1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object -2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the - requestobject, fetches the correct object from the container and calls it to create a response. - -Lets create the HandlerInterface first: - -```php -getAttribute($this->routeAttributeName, false); - assert($handler !== false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} - -``` - -We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. -The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler -as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in -the next chapter. - -```php -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} -``` - -Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. -```php -[ - '...', - Emitter::class => fn (BasicEmitter $e) => $e, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, -], -``` - -And then we can update our Bootstrap.php to fetch all the services and let them handle the request. - -```php -... -$routeMiddleWare = $container->get(MiddlewareInterface::class); -assert($routeMiddleWare instanceof MiddlewareInterface); -$handler = $container->get(RoutedRequestHandler::class); -assert($handler instanceof RequestHandlerInterface); -$emitter = $container->get(Emitter::class); -assert($emitter instanceof Emitter); - -$request = $container->get(ServerRequestInterface::class); -assert($request instanceof ServerRequestInterface); - -$response = $routeMiddleWare->process($request, $handler); -$emitter->emit($response); -``` -Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of -code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). - -So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. - -I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class -should require to function properly. - -* A RequestFactory - We want our Kernel to be able to build the request itself -* An Emitter - Without an Emitter we will not be able to send the response to the client -* RouteMiddleware - To decore the request with the correct handler for the requested route -* RequestHandler - To delegate the request to the correct funtion that creates the response - -As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface -to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our -interface: - -```php -factory::fromGlobals(); - } - - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} -``` - -For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: - -```php -routeMiddleware->process($request, $this->handler); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} - -``` - -We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines - -```php -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); -``` - -You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking -at the Whoops output and adding the needed definitions to the dependencies.php file. - -And as always, don't forget to commit your changes. - -[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/implementation/15-adding-content/data/pages/14-middleware.md b/implementation/15-adding-content/data/pages/14-middleware.md deleted file mode 100644 index e698327..0000000 --- a/implementation/15-adding-content/data/pages/14-middleware.md +++ /dev/null @@ -1,298 +0,0 @@ -[<< previous](12-refactoring.md) | [next >>](14-invoker.md) - -### Middleware - -In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain -a bit more about what this interface does and why it is used in many applications. - -The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets -passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all -the middlewars to the client/emitter. - -So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the -response after it gets created by our handlers. - -So lets take a look at the middleware and the requesthandler interfaces - -```php -interface MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; -} - -interface RequestHandlerInterface -{ - public function handle(ServerRequestInterface $request): ResponseInterface; -} -``` - -The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a -requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the -response. - -But the middleware could just ignore the handler and produce a response on its own as the interface just requires us -to produce a response. - -A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users -that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the -database. - -In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates -the request with an 'isAuthenticated' attribute. - -If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that -response is not already cached, than we let the handler create the response and store that in the cache for a few -seconds - -```php -interface CacheInterface -{ - public function get(string $key, callable $resolver, int $ttl): mixed; -} -``` - -The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one -defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses -the callable to produce the value and stores it for the time specified in ttl. - -so lets write our caching middleware: - -```php -final class CachingMiddleware implements MiddlewareInterface -{ - public function __construct(private CacheInterface $cache){} - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { - $key = $request->getUri()->getPath(); - return $this->cache->get($key, fn() => $handler->handle($request), 10); - } - return $handler->handle($request); - } -} -``` - -we can also modify the response after it has been created by our application, for example we could implement a gzip -middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser -know that our application is used to serve dank memes: - -```php -final class DankMemeMiddleware implements MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - $response = $handler->handle($request); - return $response->withAddedHeader('Meme', 'Dank'); - } -} -``` - -but for our application we are going to just add two external middlewares: - -* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. -* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware - -```bash -composer require middlewares/trailing-slash -composer require middlewares/whoops -``` - -The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the -application as well as the middleware stack. - -Our desired request -> response flow looks something like this: - - Client - | ^ - v | - Kernel - | ^ - v | - Whoops Middleware - | ^ - v | - TrailingSlash - | ^ - v | - Routing - | ^ - v | - ContainerResolver - | ^ - v | - Controller/Action - -As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every -middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. - -```php -interface Pipeline -{ - public function dispatch(ServerRequestInterface $request): ResponseInterface; -} -``` - -And our implementation looks something like this: - -```php - $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} -``` - -Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code -that should produce our response. - -In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument -and store that itself as the current tip. - -There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) - -Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline -Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline - -```php -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} -``` - -And configure the container to use the Factory to create the Pipeline: - -```php - ..., - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - ... -``` -And of course a new file called middlewares.php in our config folder: -```php -pipeline->dispatch($request); -} -``` - -Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our -pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a -dependency container, if you want you can do that as well. - -[<< previous](12-refactoring.md) | [next >>](14-invoker.md) diff --git a/implementation/15-adding-content/phpstan-baseline.neon b/implementation/15-adding-content/phpstan-baseline.neon deleted file mode 100644 index 61697a1..0000000 --- a/implementation/15-adding-content/phpstan-baseline.neon +++ /dev/null @@ -1,7 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" - count: 1 - path: src/Http/InvokerRoutedHandler.php - diff --git a/implementation/15-adding-content/phpstan.neon b/implementation/15-adding-content/phpstan.neon deleted file mode 100644 index 2eac45a..0000000 --- a/implementation/15-adding-content/phpstan.neon +++ /dev/null @@ -1,8 +0,0 @@ -includes: - - phpstan-baseline.neon - -parameters: - level: max - paths: - - src - - config \ No newline at end of file diff --git a/implementation/15-adding-content/public/css/spectre-exp.min.css b/implementation/15-adding-content/public/css/spectre-exp.min.css deleted file mode 100644 index d313774..0000000 --- a/implementation/15-adding-content/public/css/spectre-exp.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/implementation/15-adding-content/public/css/spectre-icons.min.css b/implementation/15-adding-content/public/css/spectre-icons.min.css deleted file mode 100644 index 0276f7b..0000000 --- a/implementation/15-adding-content/public/css/spectre-icons.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/implementation/15-adding-content/public/css/spectre.min.css b/implementation/15-adding-content/public/css/spectre.min.css deleted file mode 100644 index 0fe23d9..0000000 --- a/implementation/15-adding-content/public/css/spectre.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/implementation/15-adding-content/public/favicon.ico b/implementation/15-adding-content/public/favicon.ico deleted file mode 100644 index 09499b8b3b3201e0f50088e3ac42e167778d1153..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/15-adding-content/public/index.php b/implementation/15-adding-content/public/index.php deleted file mode 100644 index d93da3a..0000000 --- a/implementation/15-adding-content/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render('hello', $data); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/15-adding-content/src/Action/Other.php b/implementation/15-adding-content/src/Action/Other.php deleted file mode 100644 index da9ceaf..0000000 --- a/implementation/15-adding-content/src/Action/Other.php +++ /dev/null @@ -1,16 +0,0 @@ -parse('This *works* **too!**'); - $response->getBody()->write($html); - return $response->withStatus(200); - } -} diff --git a/implementation/15-adding-content/src/Action/Page.php b/implementation/15-adding-content/src/Action/Page.php deleted file mode 100644 index 6a3aad0..0000000 --- a/implementation/15-adding-content/src/Action/Page.php +++ /dev/null @@ -1,80 +0,0 @@ -getPages(), - fn (string $filename) => str_contains($filename, $page) - ) - )[0]; - $markdown = file_get_contents($page); - - // fix the next and previous buttons to work with our routing - $markdown = preg_replace('/\(\d\d-/m', '(', $markdown); - $markdown = str_replace('.md)', ')', $markdown); - - $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); - $data = [ - 'title' => substr($page, 3), - 'content' => $this->parser->parse($markdown), - ]; - $html = $this->renderer->render('page/show', $data); - $this->response->getBody()->write($html); - return $this->response; - } - - public function list(): ResponseInterface - { - $pages = array_map(function (string $page) { - $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); - return [ - 'id' => substr($page, 0, 2), - 'title' => substr($page, 3), - ]; - }, $this->getPages()); - $html = $this->renderer->render('page/list', ['pages' => $pages]); - $this->response->getBody()->write($html); - return $this->response; - } - - /** - * @return string[] - */ - private function getPages(): array - { - $files = glob($this->pagesPath . '*.md'); - if ($files === false) { - throw new InternalServerError('cannot read pages'); - } - return $files; - } -} diff --git a/implementation/15-adding-content/src/Bootstrap.php b/implementation/15-adding-content/src/Bootstrap.php deleted file mode 100644 index 3abc2e5..0000000 --- a/implementation/15-adding-content/src/Bootstrap.php +++ /dev/null @@ -1,40 +0,0 @@ -getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); diff --git a/implementation/15-adding-content/src/Exception/InternalServerError.php b/implementation/15-adding-content/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/implementation/15-adding-content/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ -factory::fromGlobals(); - } - - /** - * @param UriInterface|string $uri - * @param array $serverParams - */ - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} diff --git a/implementation/15-adding-content/src/Factory/DoctrineEm.php b/implementation/15-adding-content/src/Factory/DoctrineEm.php deleted file mode 100644 index b0be39b..0000000 --- a/implementation/15-adding-content/src/Factory/DoctrineEm.php +++ /dev/null @@ -1,32 +0,0 @@ -settings->doctrine['devMode']); - - $config->setMetadataDriverImpl( - new AttributeDriver( - $this->settings->doctrine['metadataDirs'] - ) - ); - - return EntityManager::create( - $this->settings->connection, - $config, - ); - } -} diff --git a/implementation/15-adding-content/src/Factory/FileSystemSettingsProvider.php b/implementation/15-adding-content/src/Factory/FileSystemSettingsProvider.php deleted file mode 100644 index f071078..0000000 --- a/implementation/15-adding-content/src/Factory/FileSystemSettingsProvider.php +++ /dev/null @@ -1,22 +0,0 @@ -filePath; - assert($settings instanceof Settings); - return $settings; - } -} diff --git a/implementation/15-adding-content/src/Factory/PipelineProvider.php b/implementation/15-adding-content/src/Factory/PipelineProvider.php deleted file mode 100644 index 77738f8..0000000 --- a/implementation/15-adding-content/src/Factory/PipelineProvider.php +++ /dev/null @@ -1,25 +0,0 @@ -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} diff --git a/implementation/15-adding-content/src/Factory/RequestFactory.php b/implementation/15-adding-content/src/Factory/RequestFactory.php deleted file mode 100644 index 2b17abc..0000000 --- a/implementation/15-adding-content/src/Factory/RequestFactory.php +++ /dev/null @@ -1,11 +0,0 @@ -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = $settings; - $builder->addDefinitions($dependencies); - // $builder->enableCompilation('/tmp'); - return $builder->build(); - } -} diff --git a/implementation/15-adding-content/src/Factory/SettingsProvider.php b/implementation/15-adding-content/src/Factory/SettingsProvider.php deleted file mode 100644 index ce1c5f0..0000000 --- a/implementation/15-adding-content/src/Factory/SettingsProvider.php +++ /dev/null @@ -1,10 +0,0 @@ -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - - echo $response->getBody(); - } -} diff --git a/implementation/15-adding-content/src/Http/ContainerPipeline.php b/implementation/15-adding-content/src/Http/ContainerPipeline.php deleted file mode 100644 index 816cedd..0000000 --- a/implementation/15-adding-content/src/Http/ContainerPipeline.php +++ /dev/null @@ -1,82 +0,0 @@ - $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} diff --git a/implementation/15-adding-content/src/Http/Emitter.php b/implementation/15-adding-content/src/Http/Emitter.php deleted file mode 100644 index ce4c035..0000000 --- a/implementation/15-adding-content/src/Http/Emitter.php +++ /dev/null @@ -1,10 +0,0 @@ -getAttribute($this->routeAttributeName, false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} diff --git a/implementation/15-adding-content/src/Http/Pipeline.php b/implementation/15-adding-content/src/Http/Pipeline.php deleted file mode 100644 index 1a9dcda..0000000 --- a/implementation/15-adding-content/src/Http/Pipeline.php +++ /dev/null @@ -1,11 +0,0 @@ -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} diff --git a/implementation/15-adding-content/src/Http/RoutedRequestHandler.php b/implementation/15-adding-content/src/Http/RoutedRequestHandler.php deleted file mode 100644 index a7407c9..0000000 --- a/implementation/15-adding-content/src/Http/RoutedRequestHandler.php +++ /dev/null @@ -1,10 +0,0 @@ -pipeline->dispatch($request); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} diff --git a/implementation/15-adding-content/src/Middleware/CacheMiddleware.php b/implementation/15-adding-content/src/Middleware/CacheMiddleware.php deleted file mode 100644 index 1bd58c5..0000000 --- a/implementation/15-adding-content/src/Middleware/CacheMiddleware.php +++ /dev/null @@ -1,40 +0,0 @@ -getMethod() === 'GET' && ! $this->settings->isDev()) { - $key = (string) $request->getUri(); - $key = base64_encode($key); - $callback = fn () => $handler->handle($request); - $cached = $this->cache->get($key, function (ItemInterface $item) use ($callback) { - $item->expiresAfter(120); - $response = $callback(); - return $this->serializer::toString($response); - }); - return $this->serializer::fromString($cached); - } - return $handler->handle($request); - } -} diff --git a/implementation/15-adding-content/src/Model/MarkdownPage.php b/implementation/15-adding-content/src/Model/MarkdownPage.php deleted file mode 100644 index bae383c..0000000 --- a/implementation/15-adding-content/src/Model/MarkdownPage.php +++ /dev/null @@ -1,21 +0,0 @@ - $this->repo->all(); - if ($this->settings->isDev()) { - return $callback(); - } - return $this->cache->get('ALLPAGES', function (ItemInterface $item) use ($callback) { - $item->expiresAfter(30); - return $callback(); - }); - } - - public function byId(int $id): MarkdownPage - { - $callback = fn () => $this->repo->byId($id); - if ($this->settings->isDev()) { - return $callback(); - } - return $this->cache->get('PAGE' . $id, function (ItemInterface $item) use ($callback) { - $item->expiresAfter(30); - return $callback(); - }); - } - - public function byTitle(string $title): MarkdownPage - { - $callback = fn () => $this->repo->byTitle($title); - if ($this->settings->isDev()) { - return $callback(); - } - return $this->cache->get('PAGE' . $title, function (ItemInterface $item) use ($callback) { - $item->expiresAfter(30); - return $callback(); - }); - } - - public function save(MarkdownPage $page): MarkdownPage - { - return $this->repo->save($page); - } -} diff --git a/implementation/15-adding-content/src/Repository/DoctrineMarkdownPageRepo.php b/implementation/15-adding-content/src/Repository/DoctrineMarkdownPageRepo.php deleted file mode 100644 index 8d1d457..0000000 --- a/implementation/15-adding-content/src/Repository/DoctrineMarkdownPageRepo.php +++ /dev/null @@ -1,59 +0,0 @@ - */ - private EntityRepository $repo; - - public function __construct( - private EntityManagerInterface $entityManager - ) { - $this->repo = $this->entityManager->getRepository(MarkdownPage::class); - } - - /** - * @inheritDoc - */ - public function all(): array - { - usleep(random_int(500, 1500) * 1000); - return $this->repo->findAll(); - } - - public function byId(int $id): MarkdownPage - { - usleep(random_int(500, 1500) * 1000); - $page = $this->repo->findOneBy(['id' => $id]); - if (! $page instanceof MarkdownPage) { - throw new NotFound; - } - return $page; - } - - public function byTitle(string $title): MarkdownPage - { - usleep(random_int(500, 1500) * 1000); - $page = $this->repo->findOneBy(['title' => $title]); - if (! $page instanceof MarkdownPage) { - throw new NotFound; - } - return $page; - } - - public function save(MarkdownPage $page): MarkdownPage - { - $this->entityManager->persist($page); - $this->entityManager->flush(); - return $page; - } -} diff --git a/implementation/15-adding-content/src/Repository/MarkdownPageFilesystem.php b/implementation/15-adding-content/src/Repository/MarkdownPageFilesystem.php deleted file mode 100644 index 25dfd97..0000000 --- a/implementation/15-adding-content/src/Repository/MarkdownPageFilesystem.php +++ /dev/null @@ -1,69 +0,0 @@ -dataPath . '*.md'); - assert(is_array($fileNames)); - return array_map(function (string $name): MarkdownPage { - usleep(random_int(200, 500) * 1000); - $content = file_get_contents($name); - $name = str_replace($this->dataPath, '', $name); - $name = str_replace('.md', '', $name); - $id = (int) substr($name, 0, 2); - $title = substr($name, 3); - return new MarkdownPage($id, $title, $content); - }, $fileNames); - } - - public function byId(int $id): MarkdownPage - { - $callback = fn (MarkdownPage $p): bool => $p->id === $id; - $filtered = array_values(array_filter($this->all(), $callback)); - if (count($filtered) === 0) { - throw new NotFound; - } - return $filtered[0]; - } - - public function byTitle(string $title): MarkdownPage - { - $callback = fn (MarkdownPage $p): bool => $p->title === $title; - $filtered = array_values(array_filter($this->all(), $callback)); - if (count($filtered) === 0) { - throw new NotFound; - } - return $filtered[0]; - } - - public function save(MarkdownPage $page): MarkdownPage - { - return $page; - } -} diff --git a/implementation/15-adding-content/src/Repository/MarkdownPageRepo.php b/implementation/15-adding-content/src/Repository/MarkdownPageRepo.php deleted file mode 100644 index 3f80899..0000000 --- a/implementation/15-adding-content/src/Repository/MarkdownPageRepo.php +++ /dev/null @@ -1,19 +0,0 @@ -environment === 'dev'; - } -} diff --git a/implementation/15-adding-content/src/Template/GithubMarkdownRenderer.php b/implementation/15-adding-content/src/Template/GithubMarkdownRenderer.php deleted file mode 100644 index 121504b..0000000 --- a/implementation/15-adding-content/src/Template/GithubMarkdownRenderer.php +++ /dev/null @@ -1,28 +0,0 @@ -addExtension($commonMarkCoreExtension); - $environment->addExtension($githubFlavoredMarkdownExtension); - $this->engine = new MarkdownConverter($environment); - } - - public function render(string $markdown): string - { - return (string) $this->engine->convert($markdown); - } -} diff --git a/implementation/15-adding-content/src/Template/MarkdownParser.php b/implementation/15-adding-content/src/Template/MarkdownParser.php deleted file mode 100644 index d404005..0000000 --- a/implementation/15-adding-content/src/Template/MarkdownParser.php +++ /dev/null @@ -1,8 +0,0 @@ -engine->render($template, $data); - } -} diff --git a/implementation/15-adding-content/src/Template/ParsedownParser.php b/implementation/15-adding-content/src/Template/ParsedownParser.php deleted file mode 100644 index 2ffd287..0000000 --- a/implementation/15-adding-content/src/Template/ParsedownParser.php +++ /dev/null @@ -1,17 +0,0 @@ -parser->parse($markdown); - } -} diff --git a/implementation/15-adding-content/src/Template/Renderer.php b/implementation/15-adding-content/src/Template/Renderer.php deleted file mode 100644 index ff916ed..0000000 --- a/implementation/15-adding-content/src/Template/Renderer.php +++ /dev/null @@ -1,11 +0,0 @@ - $data - */ - public function render(string $template, array $data = []): string; -} diff --git a/implementation/15-adding-content/templates/hello.html b/implementation/15-adding-content/templates/hello.html deleted file mode 100644 index 15a4cd2..0000000 --- a/implementation/15-adding-content/templates/hello.html +++ /dev/null @@ -1,6 +0,0 @@ -{{> partials/head }} -
-

Hello {{name}}

-

The time is {{now}}

-
-{{> partials/foot }} diff --git a/implementation/15-adding-content/templates/page.html b/implementation/15-adding-content/templates/page.html deleted file mode 100644 index c3c5284..0000000 --- a/implementation/15-adding-content/templates/page.html +++ /dev/null @@ -1,5 +0,0 @@ -{{> partials/head }} -
- {{{content}}} -
-{{> partials/foot }} diff --git a/implementation/15-adding-content/templates/page/list.html b/implementation/15-adding-content/templates/page/list.html deleted file mode 100644 index bf42348..0000000 --- a/implementation/15-adding-content/templates/page/list.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Pages - - - -
- -
- - \ No newline at end of file diff --git a/implementation/15-adding-content/templates/page/show.html b/implementation/15-adding-content/templates/page/show.html deleted file mode 100644 index abe295e..0000000 --- a/implementation/15-adding-content/templates/page/show.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - {{title}} - - - - - - -
- {{{content}}} -
- - \ No newline at end of file diff --git a/implementation/15-adding-content/templates/pagelist.html b/implementation/15-adding-content/templates/pagelist.html deleted file mode 100644 index 538e2c4..0000000 --- a/implementation/15-adding-content/templates/pagelist.html +++ /dev/null @@ -1,11 +0,0 @@ -{{> partials/head }} -
- -
-{{> partials/foot }} diff --git a/implementation/15-adding-content/templates/partials/foot.html b/implementation/15-adding-content/templates/partials/foot.html deleted file mode 100644 index 17c7245..0000000 --- a/implementation/15-adding-content/templates/partials/foot.html +++ /dev/null @@ -1,3 +0,0 @@ -
- - \ No newline at end of file diff --git a/implementation/15-adding-content/templates/partials/head.html b/implementation/15-adding-content/templates/partials/head.html deleted file mode 100644 index 421d387..0000000 --- a/implementation/15-adding-content/templates/partials/head.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - No Framework: {{title}} - - - - - -
diff --git a/implementation/16-caching/.php-cs-fixer.php b/implementation/16-caching/.php-cs-fixer.php deleted file mode 100644 index 705a7d7..0000000 --- a/implementation/16-caching/.php-cs-fixer.php +++ /dev/null @@ -1,38 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config' - ]) - ); \ No newline at end of file diff --git a/implementation/16-caching/.phpcs.xml.dist b/implementation/16-caching/.phpcs.xml.dist deleted file mode 100644 index 3b433f6..0000000 --- a/implementation/16-caching/.phpcs.xml.dist +++ /dev/null @@ -1,9 +0,0 @@ - - - - - src - config - - - \ No newline at end of file diff --git a/implementation/16-caching/composer.json b/implementation/16-caching/composer.json deleted file mode 100644 index e85cc50..0000000 --- a/implementation/16-caching/composer.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3", - "mustache/mustache": "^2.14", - "psr/http-server-middleware": "^1.0", - "middlewares/trailing-slash": "^2.0", - "middlewares/whoops": "^2.0", - "erusev/parsedown": "^1.7", - "symfony/cache": "^6.0" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require-dev": { - "phpstan/phpstan": "^1.5", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0", - "squizlabs/php_codesniffer": "^3.6", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1", - "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0" - }, - "config": { - "allow-plugins": { - "phpstan/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "serve": [ - "Composer\\Config::disableProcessTimeout", - "php -S 0.0.0.0:1234 -t public" - ], - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" - } -} diff --git a/implementation/16-caching/composer.lock b/implementation/16-caching/composer.lock deleted file mode 100644 index 0c626d9..0000000 --- a/implementation/16-caching/composer.lock +++ /dev/null @@ -1,2273 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "5286ff6a5dbbe21ace2a7359b49b8780", - "packages": [ - { - "name": "erusev/parsedown", - "version": "1.7.4", - "source": { - "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35" - }, - "type": "library", - "autoload": { - "psr-0": { - "Parsedown": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com" - } - ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org", - "keywords": [ - "markdown", - "parser" - ], - "support": { - "issues": "https://github.com/erusev/parsedown/issues", - "source": "https://github.com/erusev/parsedown/tree/1.7.x" - }, - "time": "2019-12-30T22:54:17+00:00" - }, - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.9.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2022-03-29T20:12:16+00:00" - }, - { - "name": "middlewares/trailing-slash", - "version": "v2.0.1", - "source": { - "type": "git", - "url": "https://github.com/middlewares/trailing-slash.git", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", - "shasum": "" - }, - "require": { - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to normalize the trailing slash of the uri path", - "homepage": "https://github.com/middlewares/trailing-slash", - "keywords": [ - "http", - "middleware", - "normalize", - "path", - "psr-15", - "psr-7", - "slash" - ], - "support": { - "issues": "https://github.com/middlewares/trailing-slash/issues", - "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" - }, - "time": "2020-12-02T00:06:55+00:00" - }, - { - "name": "middlewares/utils", - "version": "v3.3.0", - "source": { - "type": "git", - "url": "https://github.com/middlewares/utils.git", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^v2.16", - "guzzlehttp/psr7": "^2.0", - "laminas/laminas-diactoros": "^2.4", - "nyholm/psr7": "^1.0", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "slim/psr7": "^1.4", - "squizlabs/php_codesniffer": "^3.5", - "sunrise/http-message": "^1.0", - "sunrise/http-server-request": "^1.0", - "sunrise/stream": "^1.0.15", - "sunrise/uri": "^1.0.15" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\Utils\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Common utils for PSR-15 middleware packages", - "homepage": "https://github.com/middlewares/utils", - "keywords": [ - "PSR-11", - "http", - "middleware", - "psr-15", - "psr-17", - "psr-7" - ], - "support": { - "issues": "https://github.com/middlewares/utils/issues", - "source": "https://github.com/middlewares/utils/tree/v3.3.0" - }, - "time": "2021-07-04T17:56:23+00:00" - }, - { - "name": "middlewares/whoops", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/middlewares/whoops.git", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", - "shasum": "" - }, - "require": { - "filp/whoops": "^2.5", - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "eloquent/phony-phpunit": "^5.0 || ^7.0", - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to use Whoops as error handler", - "homepage": "https://github.com/middlewares/whoops", - "keywords": [ - "error", - "http", - "middleware", - "psr-15", - "psr-7", - "server", - "whoops" - ], - "support": { - "issues": "https://github.com/middlewares/whoops/issues", - "source": "https://github.com/middlewares/whoops/tree/v2.0.2" - }, - "time": "2022-01-27T20:31:30+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/cache", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" - }, - "time": "2021-02-03T23:26:27+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/http-server-middleware", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-middleware.git", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0", - "psr/http-server-handler": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side middleware", - "keywords": [ - "http", - "http-interop", - "middleware", - "psr", - "psr-15", - "psr-7", - "request", - "response" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-middleware/issues", - "source": "https://github.com/php-fig/http-server-middleware/tree/master" - }, - "time": "2018-10-30T17:12:04+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - }, - { - "name": "symfony/cache", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache.git", - "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", - "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "psr/cache": "^2.0|^3.0", - "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^1.1.7|^2|^3", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/var-exporter": "^5.4|^6.0" - }, - "conflict": { - "doctrine/dbal": "<2.13.1", - "symfony/dependency-injection": "<5.4", - "symfony/http-kernel": "<5.4", - "symfony/var-dumper": "<5.4" - }, - "provide": { - "psr/cache-implementation": "2.0|3.0", - "psr/simple-cache-implementation": "1.0|2.0|3.0", - "symfony/cache-implementation": "1.1|2.0|3.0" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/dbal": "^2.13.1|^3.0", - "predis/predis": "^1.1", - "psr/simple-cache": "^1.0|^2.0|^3.0", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/filesystem": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/messenger": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Cache\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", - "homepage": "https://symfony.com", - "keywords": [ - "caching", - "psr6" - ], - "support": { - "source": "https://github.com/symfony/cache/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "symfony/cache-contracts", - "version": "v3.0.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache-contracts.git", - "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/2f7463f156cf9c665d9317e21a809c3bbff5754e", - "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "psr/cache": "^3.0" - }, - "suggest": { - "symfony/cache-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Cache\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to caching", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.0.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-08-17T15:35:52+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v2.5.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8", - "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-07-12T14:48:14+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v2.5.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", - "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "suggest": { - "symfony/service-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-04T16:48:04+00:00" - }, - { - "name": "symfony/var-exporter", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-exporter.git", - "reference": "130229a482abf17635a685590958894dfb4b4360" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/130229a482abf17635a685590958894dfb4b4360", - "reference": "130229a482abf17635a685590958894dfb4b4360", - "shasum": "" - }, - "require": { - "php": ">=8.0.2" - }, - "require-dev": { - "symfony/var-dumper": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\VarExporter\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows exporting any serializable PHP data structure to plain PHP code", - "homepage": "https://symfony.com", - "keywords": [ - "clone", - "construct", - "export", - "hydrate", - "instantiate", - "serialize" - ], - "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "doctrine/coding-standard", - "version": "8.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/coding-standard.git", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "slevomat/coding-standard": "^6.4.1", - "squizlabs/php_codesniffer": "^3.5.8" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Steve Müller", - "email": "st.mueller@dzh-online.de" - } - ], - "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", - "keywords": [ - "checks", - "code", - "coding", - "cs", - "doctrine", - "rules", - "sniffer", - "sniffs", - "standard", - "style" - ], - "support": { - "issues": "https://github.com/doctrine/coding-standard/issues", - "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" - }, - "time": "2021-04-03T10:54:55+00:00" - }, - { - "name": "mnapoli/hard-mode", - "version": "0.3.0", - "source": { - "type": "git", - "url": "https://github.com/mnapoli/hard-mode.git", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", - "shasum": "" - }, - "require": { - "doctrine/coding-standard": "^8.0" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Hard mode for PHP", - "support": { - "issues": "https://github.com/mnapoli/hard-mode/issues", - "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" - }, - "time": "2020-10-12T07:54:37+00:00" - }, - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.9", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.26", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/master" - }, - "time": "2020-08-03T20:32:43+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.5.3", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/39953ac1452a8843702ee41a35b4861d3e8207a7", - "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.3" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-30T21:55:08+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^1.2.0" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" - }, - "time": "2021-11-18T09:30:29+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "6.4.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpstan/phpstan": "0.12.48", - "phpstan/phpstan-deprecation-rules": "0.12.5", - "phpstan/phpstan-phpunit": "0.12.16", - "phpstan/phpstan-strict-rules": "0.12.5", - "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2020-10-05T12:39:37+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.3.0" -} diff --git a/implementation/16-caching/config/dependencies.php b/implementation/16-caching/config/dependencies.php deleted file mode 100644 index 376aea0..0000000 --- a/implementation/16-caching/config/dependencies.php +++ /dev/null @@ -1,54 +0,0 @@ - fn (SystemClockNow $n) => $n, - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, - Emitter::class => fn (BasicEmitter $e) => $e, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, - CacheInterface::class => fn (FilesystemAdapter $a) => $a, - MarkdownPageRepo::class => fn (MarkdownPageFilesystem $r) => $r, - - // Factories - ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), - Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - MarkdownPageFilesystem::class => fn (Settings $s) => new MarkdownPageFilesystem($s->pagesPath), - CachedMarkdownPageRepo::class => fn (CacheInterface $c, MarkdownPageFilesystem $r) => new CachedMarkdownPageRepo($c, $r), -]; diff --git a/implementation/16-caching/config/middlewares.php b/implementation/16-caching/config/middlewares.php deleted file mode 100644 index 459547e..0000000 --- a/implementation/16-caching/config/middlewares.php +++ /dev/null @@ -1,13 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/page/{page}', Page::class); - $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); - $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -}; diff --git a/implementation/16-caching/config/settings.php b/implementation/16-caching/config/settings.php deleted file mode 100644 index 8a0861d..0000000 --- a/implementation/16-caching/config/settings.php +++ /dev/null @@ -1,12 +0,0 @@ ->](02-composer.md) - -### Front Controller - -A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. - -To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. - -A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. - -The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. - -But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. - -So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. - -Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: - -```php ->](02-composer.md) diff --git a/implementation/16-caching/data/pages/02-composer.md b/implementation/16-caching/data/pages/02-composer.md deleted file mode 100644 index a25a4a8..0000000 --- a/implementation/16-caching/data/pages/02-composer.md +++ /dev/null @@ -1,75 +0,0 @@ -[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) - -### Composer - -[Composer](https://getcomposer.org/) is a dependency manager for PHP. - -Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do -something. With Composer, you can install third-party libraries for your application. - -If you don't have Composer installed already, head over to the website and install it. You can find Composer packages -for your project on [Packagist](https://packagist.org/). - -Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will -be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. - -Add the following content to the file: - -```json -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubiana", - "email": "lubiana@hannover.ccc.de" - } - ] -} -``` - -In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use -whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. -Just replace it with your namespace in your own code. - -I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. - -As the Bootstrap.php file is placed in that directory we should -add the namespace to the File as well. Here is my current Bootstrap.php -as a reference: - -```php ->](03-error-handler.md) diff --git a/implementation/16-caching/data/pages/03-error-handler.md b/implementation/16-caching/data/pages/03-error-handler.md deleted file mode 100644 index 60465d0..0000000 --- a/implementation/16-caching/data/pages/03-error-handler.md +++ /dev/null @@ -1,79 +0,0 @@ -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) - -### Error Handler - -An error handler allows you to customize what happens if your code results in an error. - -A nice error page with a lot of information for debugging goes a long way during development. So the first package -for your application will take care of that. - -I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. -If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, -you have total control over your project. - -An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) - -To install a new package, open up your `composer.json` and add the package to the require part. It should now look -like this: - -```php -"require": { - "php": ">=8.1.0", - "filp/whoops": "^2.14" -}, -``` - -Now run `composer update` in your console and it will be installed. - -Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, -i that case composer automatically installs the package and updates your composer.json-file. - -But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, -ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you -only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. - -**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message -can help someone to gain access to your system. Always show a user friendly error page instead and send an email to -yourself, write to a log or something similar. So only you can see the errors in the production environment. - -For development that does not make sense though -- you want a nice error page. The solution is to have an environment -switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in -case no environment has been set. - -Then after the error handler registration, throw an `Exception` to test if everything is working correctly. -Your `Bootstrap.php` should now look similar to this: - -```php -pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (\Throwable $e) { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -throw new \Exception("Ooooopsie"); - -``` - -You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until -you get it working. Now would also be a good time for another commit. - - -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/implementation/16-caching/data/pages/04-development-helpers.md b/implementation/16-caching/data/pages/04-development-helpers.md deleted file mode 100644 index 74f913c..0000000 --- a/implementation/16-caching/data/pages/04-development-helpers.md +++ /dev/null @@ -1,260 +0,0 @@ -[<< previous](03-error-handler.md) | [next >>](05-http.md) - -### Development Helpers - -I have added some more helpers to my composer.json that help me with development. As these are scripts and programms -used only for development they should not be used in a production environment. Composer has a specific sections in its -file called "dev-dependencies", everything that is required in this section does not get installen in production. - -Let's install our dev-helpers and i will explain them one by one: -`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` - -#### Static Code Analysis with phpstan - -Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or -create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements -that are not (or not always) available, if-statements that always are true and a lot of other stuff. - -A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. - -```php -/** - * @param \DateTime $date - * @return void - */ -function printDate($date) { - $date->format('Y-m-d H:i:s'); -} - -printDate('now'); -``` -if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` - -It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has -no use, and secondly, that we are calling the function with a string instead of a datetime object. - -```shell -1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - ------ --------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ --------------------------------------------------------------------------------------------- -30 Call to method DateTime::format() on a separate line has no effect. -33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. - ------ --------------------------------------------------------------------------------------------- -``` - -The second error is something that "declare strict-types" already catches for us, but the first error is something that -we usually would not discover easily without speccially looking for this errortype. - -We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and -path everytime we want to check our code for errors: - -```yaml -parameters: - level: max - paths: - - src -``` -now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project - -With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us -some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. - -```shell -composer require --dev phpstan/extension-installer -composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules -``` - -During the first install you need to allow the extension installer to actually install the extension. The second command -installs some more strict rulesets and activates them in phpstan. - -If we now rerun phpstan it already tells us about some errors we have made: - -``` - ------ ----------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ ----------------------------------------------------------------------------------------------- -10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider - using long ternary. -25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: - http://bit.ly/subtypeexception -26 Unreachable statement - code above always terminates. - ------ ----------------------------------------------------------------------------------------------- -``` - -The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove -that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific -problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working -on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages -everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we -are adding to our code. - -In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the -phpstan.neon file and run phpstan with the -'--generate-baseline' option: - -```yaml -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` -```shell -[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline -Note: Using configuration file /home/vagrant/app/phpstan.neon. - 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - - - [OK] Baseline generated with 1 error. - - -``` - -you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) - -#### PHP-CS-Fixer - -Another great tool is the php-cs-fixer, which just applies a specific style to your code. - -when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current -directory. - -You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) - -personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup -a configuration file that i use in all my projects .php-cs-fixer.php: - -```php -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - ]) - ); -``` - -#### PHP Codesniffer - -The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra -rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. - -it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. - -Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have -guessed we are adding some extra rules. - -Lets install the ruleset with composer -```shell -composer require --dev mnapoli/hard-mode -``` - -and add a configuration file to actually use it '.phpcs.xml.dist' -```xml - - - - - src - - - -``` - -running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. - -``` -[vagrant@archlinux app]$ ./vendor/bin/phpcs - -FILE: src/Bootstrap.php ----------------------------------------------------------------------------------------------------- -FOUND 4 ERRORS AFFECTING 4 LINES ----------------------------------------------------------------------------------------------------- - 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. - 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead ----------------------------------------------------------------------------------------------------- -PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY ----------------------------------------------------------------------------------------------------- - -Time: 639ms; Memory: 10MB -``` - -You can then use `./vendor/bin/phpcbf` to try to fix them - - -#### Symfony Var-Dumper - -another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small -functions. - -dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects -or arrays. - -dd() on the other hand is a function that dumps its parameters and then exits the php-script. - -you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. - -#### Composer scripts - -now we have a few commands that are available on the command line. i personally do not like to type complex commands -with lots of parameters by hand all the time, so i added a few lines to my composer.json: - -```json -"scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" -}, -``` - -that way i can just type "composer" followed by the command name in the root of my project. if i want to start the -php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the -time. - -You could also configure PhpStorm to automatically run these commands in the background and highlight the violations -directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my -flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. - -My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before -commiting and pushing the code. - -[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/implementation/16-caching/data/pages/05-http.md b/implementation/16-caching/data/pages/05-http.md deleted file mode 100644 index 6166214..0000000 --- a/implementation/16-caching/data/pages/05-http.md +++ /dev/null @@ -1,124 +0,0 @@ -[<< previous](04-development-helpers.md) | [next >>](06-router.md) - -### HTTP - -PHP already has a few things built in to make working with HTTP easier. For example there are the -[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. - -These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, -if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, -then you will want a class with a nice object-oriented interface that you can use in your application instead. - -Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The -standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php -projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. - -As this is a widely adopted standard there are already several implementations available for us to use. I will choose -the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. - -Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a -[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. - -Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use -that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding -the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). - - -to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit -enter - -Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): - -```php -$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); -$response = new \Laminas\Diactoros\Response; -$response->getBody()->write('Hello World! '); -$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); -``` - -This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. - -In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the -write()-Method on that object. - - -To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: - -```php -echo $response->getBody(); -``` - -This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only -stores data. - -You can play around with the other methods of the Request object and take a look at its content with the dd() function. - -```php -dd($response) -``` - -Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot -be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which -creates a copy of the request object with the changed property and returns that clone: - -```php -$response = $response->withStatus(200); -$response = $response->withAddedHeader('Content-type', 'application/json'); -``` - -If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been -defined this way. - -But if you have been keeping attention you might argue that the following line should not work if the request object is -immutable. - -```php -$response->getBody()->write('Hello World!'); -``` - -The response-body implements a stream interface which is immutable for some reasons that are described in the -[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be -aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in -the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. -I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content -to a response object. - -```php -$body = $response->getBody(); -$body->write('Hello World!'); -$response = $response->withBody($body); -``` - -Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our -output-logic a little bit more. Replace the line that echos the response-body with the following: - -```php -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); -``` - -This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a -webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. - -Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. - -Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter - - -[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/implementation/16-caching/data/pages/06-router.md b/implementation/16-caching/data/pages/06-router.md deleted file mode 100644 index 6c39ae5..0000000 --- a/implementation/16-caching/data/pages/06-router.md +++ /dev/null @@ -1,101 +0,0 @@ -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) - -### Router - -A router dispatches to different handlers depending on rules that you have set up. - -With your current setup it does not matter what URL is used to access the application, it will always result in the same -response. So let's fix that now. - -I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own -favorite package. - -Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) - -By now you know how to install Composer packages, so I will leave that to you. - -Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. - -```php -$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -switch ($routeInfo[0]) { - case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: - $response = (new \Laminas\Diactoros\Response)->withStatus(405); - $response->getBody()->write('Method not allowed'); - $response = $response->withStatus(405); - break; - case \FastRoute\Dispatcher::FOUND: - $handler = $routeInfo[1]; - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - /** @var \Psr\Http\Message\ResponseInterface $response */ - $response = call_user_func($handler, $request); - break; - case \FastRoute\Dispatcher::NOT_FOUND: - default: - $response = (new \Laminas\Diactoros\Response)->withStatus(404); - $response->getBody()->write('Not Found!'); - break; -} -``` - -In the first part of the code, you are registering the available routes for your application. In the second part, the -dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, -we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. -If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. - -This setup might work for really small applications, but once you start adding a few routes your bootstrap file will -quickly get cluttered. So let's move them out into a separate file. - -Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; - -```php -addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}; -``` - -Now let's rewrite the route dispatcher part to use the `Routes.php` file. - -```php -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); -``` - -This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. - -Of course we now need to add the 'config' folder to the configuration files of our -devhelpers so that they can scan that directory as well. - -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/implementation/16-caching/data/pages/07-dispatching-to-a-class.md b/implementation/16-caching/data/pages/07-dispatching-to-a-class.md deleted file mode 100644 index 0c961a4..0000000 --- a/implementation/16-caching/data/pages/07-dispatching-to-a-class.md +++ /dev/null @@ -1,137 +0,0 @@ -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) - -### Dispatching to a Class - -In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). -MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to -learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) -and the followup posts. - -So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). - -We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other -common names are 'Controllers' or 'Actions'. - -Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. -In there, create a `Hello.php` file. - -```php -getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - } -} -``` - -You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) -that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is -fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement -it in some other parts of our application as well. In order to use that Interface we have to require it with composer: -'composer require psr/http-server-handler'. - -The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. -At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. - -Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: - -```php -return function(\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); - $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); -}; -``` - -Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared -the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing -the response to the one we defined for the second route. - -To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: - -```php -case \FastRoute\Dispatcher::FOUND: - $handler = new $routeInfo[1]; - if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { - throw new \Exception('Invalid Requesthandler'); - } - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - assert($response instanceof \Psr\Http\Message\ResponseInterface) - break; -``` - -So instead of just calling a method you are now instantiating an object and then calling the method on it. - -Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. - -And of course don't forget to commit your changes. - -Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still -generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for -example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still -have our whoopsie error-handler but i like to be more explicit in my control flow. - -In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new -Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and -InternalServerError. All three should extend phps Base Exception class. - -Here is my NotFound.php for example. - -```php - $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - break; - case Dispatcher::METHOD_NOT_ALLOWED: - throw new MethodNotAllowed; - - case Dispatcher::NOT_FOUND: - default: - throw new NotFound; - } -} catch (MethodNotAllowed) { - $response = (new Response)->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = (new Response)->withStatus(404); - $response->getBody()->write('Not Found'); -} catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); -} -``` - -Check if our code still works, try to trigger some errors, run phpstan and the fix command -and don't forget to commit your changes. - -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/implementation/16-caching/data/pages/08-inversion-of-control.md b/implementation/16-caching/data/pages/08-inversion-of-control.md deleted file mode 100644 index 21f4f23..0000000 --- a/implementation/16-caching/data/pages/08-inversion-of-control.md +++ /dev/null @@ -1,54 +0,0 @@ -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) - -### Inversion of Control - -In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we -want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then -we would need to edit every one of our request handlers to call a different constructor of the class. - -The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that -instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done -with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). - -If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is -implemented, it will make sense. - -Change your `Hello` action to the following: - -```php -getAttribute('name', 'Stranger'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: - -```php -$handler = new $className($response); -``` - -Of course we need to also update all the other handlers. - -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/implementation/16-caching/data/pages/09-dependency-injector.md b/implementation/16-caching/data/pages/09-dependency-injector.md deleted file mode 100644 index 7f7c6a2..0000000 --- a/implementation/16-caching/data/pages/09-dependency-injector.md +++ /dev/null @@ -1,213 +0,0 @@ -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) - -### Dependency Injector - -A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when -the class is instantiated. - -Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work -with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look -for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). - -I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) -out of the box. - -After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: - -```php -addDefinitions([ - \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), - \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), -]); - -return $builder->build(); -``` - -In this file we create a containerbuilder, add some definitions to it and return the container. -As the container supports autowiring we only need to define services where we want to use a specific implementation of -an interface. - -In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to -tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second -exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. - -Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), -as we will use that extensively. - -Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally -to create our Handler-Object - -```php -$container = require __DIR__ . '/../config/dependencies.php'; -assert($container instanceof \Psr\Container\ContainerInterface); - -$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); -assert($request instanceof \Psr\Http\Message\ServerRequestInterface); -``` - -The other part that has to be changed is the dispatching of the route. Before you had the following code: - -```php -$className = $routeInfo[1]; -$handler = new $className($response); -assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Change that to the following: - -```php -/** @var RequestHandlerInterface $handler */ -$className = $routeInfo[1]; -$handler = $container->get($className); -assert($handler instanceof RequestHandlerInterface); -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Make sure to use the container fetch the response object in the catch blocks as well: - -```php -} catch (MethodNotAllowed) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(404); - $response->getBody()->write('Not Found'); -} -``` - -Now all your controller constructor dependencies will be automatically resolved with PHP-DI. - -We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons -in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call -`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we -want to work with a different date in a test. - -Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation -that creates us a DateTimeImmutable object with the current date and time. - -src/Service/Time/Now.php: -```php -namespace Lubian\NoFramework\Service\Time; - -interface Now -{ - public function __invoke(): \DateTimeImmutable; -} -``` -src/Service/Time/SystemClockNow.php: -```php -namespace Lubian\NoFramework\Service\Time; - -final class SystemClockNow implements Now -{ - - public function __invoke(): \DateTimeImmutable - { - return new \DateTimeImmutable; - } -} -``` -If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and -update the handle-method to use the new class property: - -```php -getAttribute('name', 'Stranger'); - $nowAsString = ($this->now)()->format('H:i:s'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowAsString); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI -automatically figures out what classes are requested in the constructor and tries to create the objects needed. - -But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred -S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: - -```php - public function __construct( - private ResponseInterface $response, - private Now $now, - ) -``` - -When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation -should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: - -```php -\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), -``` - -we could also use the PHP-DI create method to delegate the object creation to the container implementation: -```php -\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), -``` - -this way the container can try to resolve any dependencies that the class might have internally, but prefer the other -method because we are not depending on this specific dependency injection implementation. - -Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are -requesting the Hello action. - -If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As -we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way -we wont get any warnings for this particular issue, but for any other issues we add to our code. - -Update the phpstan.neon file to include a "baseline" file: - -``` -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` - -if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and -ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just -'baseline' - -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/implementation/16-caching/data/pages/10-invoker.md b/implementation/16-caching/data/pages/10-invoker.md deleted file mode 100644 index 3033fae..0000000 --- a/implementation/16-caching/data/pages/10-invoker.md +++ /dev/null @@ -1,102 +0,0 @@ -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) - -### Invoker - -Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the -one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long -with some services and not the whole Requestobject with all its various properties. - -If we take our Hello action for example we only need a response object, the time service and the 'name' information from -the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named -the class hello and it would be redundant to also call the the method hello. So an updated version of that class could -look like this: - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - string $name = 'Stranger', - ): ResponseInterface - { - $body = $this->response->getBody(); - $nowString = $now->get()->format('H:i:s'); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowString); - return $response - ->withBody($body) - ->withStatus(200); - } -} -``` - -It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short -closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the -rootpath of our application yet. - -```php -$r->addRoute('GET', '/hello[/{name}]', Hello::class); -$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); -$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -``` - -In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the -route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that -class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what -arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router -if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple -callable we would need to manually use reflection as well to resolve all the arguments. - -But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) -which handles all of that for us. - -After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo -switch section in the Bootstrap.php file to use the container->call() method: - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2]; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$response = $container->call($handler, $args); -``` - -Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. - -But by now you should know that I do not like to depend on specific implementations and the call method is not defined in -the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container -or any other implementation. - -Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific -container implementation so we could use it with any container that implements the ContainerInterface. And best of all -the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that -we could implement if we ever want to write our own implementation or we could write an adapter that uses a different -class that solves the same problem. - -But for now we are using the solution provided by PHP-DI. -So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2] ?? []; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$invoker = $container->get(InvokerInterface::class); -assert($invoker instanceof InvokerInterface); -$response = $invoker->call($handler, $args); -assert($response instanceof ResponseInterface); -``` - -Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) -by php, and even some more. - -But let us move on to something more fun and add some templating functionality to our application as we are trying to build -a website in the end. - -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/implementation/16-caching/data/pages/11-templating.md b/implementation/16-caching/data/pages/11-templating.md deleted file mode 100644 index 3759664..0000000 --- a/implementation/16-caching/data/pages/11-templating.md +++ /dev/null @@ -1,240 +0,0 @@ -[<< previous](10-invoker.md) | [next >>](12-configuration.md) - -### Templating - -A template engine is not necessary with PHP because the language itself can take care of that. But it can make things -like escaping values easier. They also make it easier to draw a clear line between your application logic and the -template files which should only put your variables into the HTML code. - -A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please -also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. -Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. - -For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install -that package before you continue (`composer require mustache/mustache`). - -Another well known alternative would be [Twig](http://twig.sensiolabs.org/). - -Now please go and have a look at the source code of the -[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class -does not implement an interface. - -You could just type hint against the concrete class. But the problem with this approach is that you create tight -coupling. - -In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the -implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want -to add functionality to the engine. You can't do that without going back and changing all your code that is tightly -coupled. - -What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need -another implementation, you just implement that interface in your new class and inject the new class instead. - -Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). -This sounds a lot more complicated than it is, so just follow along. - -First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). -This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. -A class can implement multiple interfaces if necessary. - -So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a -new folder in your `src/` folder with the name `Template` where you can put all the template related things. - -In there create a new interface `Renderer.php` that looks like this: - -```php - $data - * @return string - */ - public function render(string $template, array $data = []) : string; -} -``` - -Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file -`MustacheRenderer.php` with the following content: - -```php -engine->render($template, $data); - } -} -``` - -As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple -and only fulfills the interface. - -Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know -which implementation he has to inject when you hint for the interface. Add this line: - -```php -[ - ... - \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) - ->constructor(new Mustache_Engine), -] -``` - -Now update the Hello.php class to require an implementation of our renderer interface -and use that to render a string using mustache syntax. - - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - Renderer $renderer, - string $name = 'Stranger', - ): ResponseInterface { - $body = $response->getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render( - 'Hello {{name}}, the time is {{now}}!', - $data, - ); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} -``` - -Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. -But what we want is template files, so let's go back and change that. - -To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the -`dependencies.php` file and add the following code: - -```php -[ - ... - Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), - Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), -] -``` - -We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. -Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have -to rename all the template files. - -To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the -dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader -in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object -we can then use it in the factory. - -In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: - -``` -

Hello World

-Hello {{ name }} -``` - -Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` - -Navigate to the hello page in your browser to make sure everything works. - -One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies -file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration -values. - -Lets create a 'Settings' class in our './src' Folder: - -```php -addDefinitions([ - Settings::class => fn () => require __DIR__ '/settings.php', - ResponseInterface::class => create(Response::class), - ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), - Renderer::class => fn (ME $me) => new Mustache($me), - MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), -]); - -return $builder->build(); -``` - - - -And as always, don't forget to commit your changes. - - -[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/implementation/16-caching/data/pages/12-configuration.md b/implementation/16-caching/data/pages/12-configuration.md deleted file mode 100644 index a44dfd5..0000000 --- a/implementation/16-caching/data/pages/12-configuration.md +++ /dev/null @@ -1,201 +0,0 @@ -[<< previous](11-templating.md) | [next >>](13-refactoring.md) - -### Configuration - -In the last chapter we added some more definitions to our dependencies.php in that definitions -we needed to pass quite a few configuration settings and filesystem strings to the constructors -of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. - -As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. - -As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. - -Lets create a 'Settings' class in our './src' Folder: - -```php -filePath; - } -} -``` - -If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files -and craft a settings object from them. - -As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another -factory for our Container because everyone should have a Friend :) - -```php -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn () => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} -``` - -For this to work we need to change our dependencies.php file to just return the array of definitions: -And here we can instantly use the Settings object to create our template engine. - -```php - fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), -]; -``` - -Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: -require __DIR__ . '/../vendor/autoload.php'; - -```php -... -error_reporting(E_ALL); - -$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); -$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); -... -``` - -Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. - -[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/implementation/16-caching/data/pages/13-refactoring.md b/implementation/16-caching/data/pages/13-refactoring.md deleted file mode 100644 index 067e168..0000000 --- a/implementation/16-caching/data/pages/13-refactoring.md +++ /dev/null @@ -1,377 +0,0 @@ -[<< previous](12-configuration.md) | [next >>](14-middleware.md) - -### Refactoring - -By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no -reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. -After all the bootstrap file should just set up the classes needed for the handling logic and execute them. - -At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. -As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the -method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non -static and extracted an interface. - -'./src/Http/Emitter.php' -```php -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - echo $response->getBody(); - } -} -``` -After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following -code in the Bootstrap.php to emit the response: - -```php -/** @var Emitter $emitter */ -$emitter = $container->get(Emitter::class); -$emitter->emit($response); -``` - -If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily -write an adapter that implements your emitter interface and wraps that more advanced emitter - -Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and -calling the routerhandler that in the passes the request to a function and gets the response. - -For this to steps to be seperated we are going to create two more classes: -1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object -2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the - requestobject, fetches the correct object from the container and calls it to create a response. - -Lets create the HandlerInterface first: - -```php -getAttribute($this->routeAttributeName, false); - assert($handler !== false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} - -``` - -We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. -The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler -as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in -the next chapter. - -```php -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} -``` - -Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. -```php -[ - '...', - Emitter::class => fn (BasicEmitter $e) => $e, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, -], -``` - -And then we can update our Bootstrap.php to fetch all the services and let them handle the request. - -```php -... -$routeMiddleWare = $container->get(MiddlewareInterface::class); -assert($routeMiddleWare instanceof MiddlewareInterface); -$handler = $container->get(RoutedRequestHandler::class); -assert($handler instanceof RequestHandlerInterface); -$emitter = $container->get(Emitter::class); -assert($emitter instanceof Emitter); - -$request = $container->get(ServerRequestInterface::class); -assert($request instanceof ServerRequestInterface); - -$response = $routeMiddleWare->process($request, $handler); -$emitter->emit($response); -``` -Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of -code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). - -So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. - -I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class -should require to function properly. - -* A RequestFactory - We want our Kernel to be able to build the request itself -* An Emitter - Without an Emitter we will not be able to send the response to the client -* RouteMiddleware - To decore the request with the correct handler for the requested route -* RequestHandler - To delegate the request to the correct funtion that creates the response - -As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface -to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our -interface: - -```php -factory::fromGlobals(); - } - - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} -``` - -For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: - -```php -routeMiddleware->process($request, $this->handler); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} - -``` - -We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines - -```php -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); -``` - -You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking -at the Whoops output and adding the needed definitions to the dependencies.php file. - -And as always, don't forget to commit your changes. - -[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/implementation/16-caching/data/pages/14-middleware.md b/implementation/16-caching/data/pages/14-middleware.md deleted file mode 100644 index e698327..0000000 --- a/implementation/16-caching/data/pages/14-middleware.md +++ /dev/null @@ -1,298 +0,0 @@ -[<< previous](12-refactoring.md) | [next >>](14-invoker.md) - -### Middleware - -In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain -a bit more about what this interface does and why it is used in many applications. - -The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets -passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all -the middlewars to the client/emitter. - -So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the -response after it gets created by our handlers. - -So lets take a look at the middleware and the requesthandler interfaces - -```php -interface MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; -} - -interface RequestHandlerInterface -{ - public function handle(ServerRequestInterface $request): ResponseInterface; -} -``` - -The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a -requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the -response. - -But the middleware could just ignore the handler and produce a response on its own as the interface just requires us -to produce a response. - -A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users -that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the -database. - -In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates -the request with an 'isAuthenticated' attribute. - -If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that -response is not already cached, than we let the handler create the response and store that in the cache for a few -seconds - -```php -interface CacheInterface -{ - public function get(string $key, callable $resolver, int $ttl): mixed; -} -``` - -The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one -defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses -the callable to produce the value and stores it for the time specified in ttl. - -so lets write our caching middleware: - -```php -final class CachingMiddleware implements MiddlewareInterface -{ - public function __construct(private CacheInterface $cache){} - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { - $key = $request->getUri()->getPath(); - return $this->cache->get($key, fn() => $handler->handle($request), 10); - } - return $handler->handle($request); - } -} -``` - -we can also modify the response after it has been created by our application, for example we could implement a gzip -middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser -know that our application is used to serve dank memes: - -```php -final class DankMemeMiddleware implements MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - $response = $handler->handle($request); - return $response->withAddedHeader('Meme', 'Dank'); - } -} -``` - -but for our application we are going to just add two external middlewares: - -* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. -* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware - -```bash -composer require middlewares/trailing-slash -composer require middlewares/whoops -``` - -The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the -application as well as the middleware stack. - -Our desired request -> response flow looks something like this: - - Client - | ^ - v | - Kernel - | ^ - v | - Whoops Middleware - | ^ - v | - TrailingSlash - | ^ - v | - Routing - | ^ - v | - ContainerResolver - | ^ - v | - Controller/Action - -As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every -middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. - -```php -interface Pipeline -{ - public function dispatch(ServerRequestInterface $request): ResponseInterface; -} -``` - -And our implementation looks something like this: - -```php - $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} -``` - -Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code -that should produce our response. - -In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument -and store that itself as the current tip. - -There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) - -Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline -Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline - -```php -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} -``` - -And configure the container to use the Factory to create the Pipeline: - -```php - ..., - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - ... -``` -And of course a new file called middlewares.php in our config folder: -```php -pipeline->dispatch($request); -} -``` - -Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our -pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a -dependency container, if you want you can do that as well. - -[<< previous](12-refactoring.md) | [next >>](14-invoker.md) diff --git a/implementation/16-caching/phpstan-baseline.neon b/implementation/16-caching/phpstan-baseline.neon deleted file mode 100644 index 61697a1..0000000 --- a/implementation/16-caching/phpstan-baseline.neon +++ /dev/null @@ -1,7 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" - count: 1 - path: src/Http/InvokerRoutedHandler.php - diff --git a/implementation/16-caching/phpstan.neon b/implementation/16-caching/phpstan.neon deleted file mode 100644 index 2eac45a..0000000 --- a/implementation/16-caching/phpstan.neon +++ /dev/null @@ -1,8 +0,0 @@ -includes: - - phpstan-baseline.neon - -parameters: - level: max - paths: - - src - - config \ No newline at end of file diff --git a/implementation/16-caching/public/css/spectre-exp.min.css b/implementation/16-caching/public/css/spectre-exp.min.css deleted file mode 100644 index d313774..0000000 --- a/implementation/16-caching/public/css/spectre-exp.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/implementation/16-caching/public/css/spectre-icons.min.css b/implementation/16-caching/public/css/spectre-icons.min.css deleted file mode 100644 index 0276f7b..0000000 --- a/implementation/16-caching/public/css/spectre-icons.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/implementation/16-caching/public/css/spectre.min.css b/implementation/16-caching/public/css/spectre.min.css deleted file mode 100644 index 0fe23d9..0000000 --- a/implementation/16-caching/public/css/spectre.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/implementation/16-caching/public/favicon.ico b/implementation/16-caching/public/favicon.ico deleted file mode 100644 index 09499b8b3b3201e0f50088e3ac42e167778d1153..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/16-caching/public/index.php b/implementation/16-caching/public/index.php deleted file mode 100644 index d93da3a..0000000 --- a/implementation/16-caching/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render('hello', $data); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/16-caching/src/Action/Other.php b/implementation/16-caching/src/Action/Other.php deleted file mode 100644 index 895796e..0000000 --- a/implementation/16-caching/src/Action/Other.php +++ /dev/null @@ -1,19 +0,0 @@ -getBody(); - - $body->write('This works too!'); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/16-caching/src/Action/Page.php b/implementation/16-caching/src/Action/Page.php deleted file mode 100644 index e3461ad..0000000 --- a/implementation/16-caching/src/Action/Page.php +++ /dev/null @@ -1,35 +0,0 @@ -byTitle($page); - $content = $this->linkFilter($page->content); - $content = $parsedown->parse($content); - $html = $renderer->render('page', ['content' => $content, 'title' => $page->title]); - $response->getBody()->write($html); - return $response; - } - - private function linkFilter(string $content): string - { - $content = preg_replace('/\(\d\d-/m', '(', $content); - return str_replace('.md)', ')', $content); - } -} diff --git a/implementation/16-caching/src/Bootstrap.php b/implementation/16-caching/src/Bootstrap.php deleted file mode 100644 index 3abc2e5..0000000 --- a/implementation/16-caching/src/Bootstrap.php +++ /dev/null @@ -1,40 +0,0 @@ -getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); diff --git a/implementation/16-caching/src/Exception/InternalServerError.php b/implementation/16-caching/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/implementation/16-caching/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ -factory::fromGlobals(); - } - - /** - * @param UriInterface|string $uri - * @param array $serverParams - */ - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} diff --git a/implementation/16-caching/src/Factory/FileSystemSettingsProvider.php b/implementation/16-caching/src/Factory/FileSystemSettingsProvider.php deleted file mode 100644 index f071078..0000000 --- a/implementation/16-caching/src/Factory/FileSystemSettingsProvider.php +++ /dev/null @@ -1,22 +0,0 @@ -filePath; - assert($settings instanceof Settings); - return $settings; - } -} diff --git a/implementation/16-caching/src/Factory/PipelineProvider.php b/implementation/16-caching/src/Factory/PipelineProvider.php deleted file mode 100644 index 77738f8..0000000 --- a/implementation/16-caching/src/Factory/PipelineProvider.php +++ /dev/null @@ -1,25 +0,0 @@ -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} diff --git a/implementation/16-caching/src/Factory/RequestFactory.php b/implementation/16-caching/src/Factory/RequestFactory.php deleted file mode 100644 index 2b17abc..0000000 --- a/implementation/16-caching/src/Factory/RequestFactory.php +++ /dev/null @@ -1,11 +0,0 @@ -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn (): Settings => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} diff --git a/implementation/16-caching/src/Factory/SettingsProvider.php b/implementation/16-caching/src/Factory/SettingsProvider.php deleted file mode 100644 index ce1c5f0..0000000 --- a/implementation/16-caching/src/Factory/SettingsProvider.php +++ /dev/null @@ -1,10 +0,0 @@ -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - - echo $response->getBody(); - } -} diff --git a/implementation/16-caching/src/Http/ContainerPipeline.php b/implementation/16-caching/src/Http/ContainerPipeline.php deleted file mode 100644 index 816cedd..0000000 --- a/implementation/16-caching/src/Http/ContainerPipeline.php +++ /dev/null @@ -1,82 +0,0 @@ - $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} diff --git a/implementation/16-caching/src/Http/Emitter.php b/implementation/16-caching/src/Http/Emitter.php deleted file mode 100644 index ce4c035..0000000 --- a/implementation/16-caching/src/Http/Emitter.php +++ /dev/null @@ -1,10 +0,0 @@ -getAttribute($this->routeAttributeName, false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} diff --git a/implementation/16-caching/src/Http/Pipeline.php b/implementation/16-caching/src/Http/Pipeline.php deleted file mode 100644 index 1a9dcda..0000000 --- a/implementation/16-caching/src/Http/Pipeline.php +++ /dev/null @@ -1,11 +0,0 @@ -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} diff --git a/implementation/16-caching/src/Http/RoutedRequestHandler.php b/implementation/16-caching/src/Http/RoutedRequestHandler.php deleted file mode 100644 index a7407c9..0000000 --- a/implementation/16-caching/src/Http/RoutedRequestHandler.php +++ /dev/null @@ -1,10 +0,0 @@ -pipeline->dispatch($request); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} diff --git a/implementation/16-caching/src/Middleware/CacheMiddleware.php b/implementation/16-caching/src/Middleware/CacheMiddleware.php deleted file mode 100644 index edf67d7..0000000 --- a/implementation/16-caching/src/Middleware/CacheMiddleware.php +++ /dev/null @@ -1,36 +0,0 @@ -getMethod() === 'GET') { - $key = (string) $request->getUri(); - $key = base64_encode($key); - $callback = fn () => $handler->handle($request); - $cached = $this->cache->get($key, function (ItemInterface $item) use ($callback) { - $item->expiresAfter(120); - $response = $callback(); - return $this->serializer::toString($response); - }); - return $this->serializer::fromString($cached); - } - return $handler->handle($request); - } -} diff --git a/implementation/16-caching/src/Model/MarkdownPage.php b/implementation/16-caching/src/Model/MarkdownPage.php deleted file mode 100644 index 503774f..0000000 --- a/implementation/16-caching/src/Model/MarkdownPage.php +++ /dev/null @@ -1,13 +0,0 @@ - $this->repo->all(); - return $this->cache->get('ALLPAGES', function (ItemInterface $item) use ($callback) { - $item->expiresAfter(30); - return $callback(); - }); - } - - public function byId(int $id): MarkdownPage - { - $callback = fn () => $this->repo->byId($id); - return $this->cache->get('PAGE' . $id, function (ItemInterface $item) use ($callback) { - $item->expiresAfter(30); - return $callback(); - }); - } - - public function byTitle(string $title): MarkdownPage - { - $callback = fn () => $this->repo->byTitle($title); - return $this->cache->get('PAGE' . $title, function (ItemInterface $item) use ($callback) { - $item->expiresAfter(30); - return $callback(); - }); - } -} diff --git a/implementation/16-caching/src/Repository/MarkdownPageFilesystem.php b/implementation/16-caching/src/Repository/MarkdownPageFilesystem.php deleted file mode 100644 index abd4107..0000000 --- a/implementation/16-caching/src/Repository/MarkdownPageFilesystem.php +++ /dev/null @@ -1,63 +0,0 @@ -dataPath . '*.md'); - assert(is_array($fileNames)); - return array_map(function (string $name): MarkdownPage { - usleep(100000); - $content = file_get_contents($name); - $name = str_replace($this->dataPath, '', $name); - $name = str_replace('.md', '', $name); - $id = (int) substr($name, 0, 2); - $title = substr($name, 3); - return new MarkdownPage($id, $title, $content); - }, $fileNames); - } - - public function byId(int $id): MarkdownPage - { - $callback = fn (MarkdownPage $p): bool => $p->id === $id; - $filtered = array_values(array_filter($this->all(), $callback)); - if (count($filtered) === 0) { - throw new NotFound; - } - return $filtered[0]; - } - - public function byTitle(string $title): MarkdownPage - { - $callback = fn (MarkdownPage $p): bool => $p->title === $title; - $filtered = array_values(array_filter($this->all(), $callback)); - if (count($filtered) === 0) { - throw new NotFound; - } - return $filtered[0]; - } -} diff --git a/implementation/16-caching/src/Repository/MarkdownPageRepo.php b/implementation/16-caching/src/Repository/MarkdownPageRepo.php deleted file mode 100644 index b823af0..0000000 --- a/implementation/16-caching/src/Repository/MarkdownPageRepo.php +++ /dev/null @@ -1,17 +0,0 @@ -engine->render($template, $data); - } -} diff --git a/implementation/16-caching/src/Template/Renderer.php b/implementation/16-caching/src/Template/Renderer.php deleted file mode 100644 index ff916ed..0000000 --- a/implementation/16-caching/src/Template/Renderer.php +++ /dev/null @@ -1,11 +0,0 @@ - $data - */ - public function render(string $template, array $data = []): string; -} diff --git a/implementation/16-caching/templates/hello.html b/implementation/16-caching/templates/hello.html deleted file mode 100644 index 15a4cd2..0000000 --- a/implementation/16-caching/templates/hello.html +++ /dev/null @@ -1,6 +0,0 @@ -{{> partials/head }} -
-

Hello {{name}}

-

The time is {{now}}

-
-{{> partials/foot }} diff --git a/implementation/16-caching/templates/page.html b/implementation/16-caching/templates/page.html deleted file mode 100644 index c3c5284..0000000 --- a/implementation/16-caching/templates/page.html +++ /dev/null @@ -1,5 +0,0 @@ -{{> partials/head }} -
- {{{content}}} -
-{{> partials/foot }} diff --git a/implementation/16-caching/templates/partials/foot.html b/implementation/16-caching/templates/partials/foot.html deleted file mode 100644 index 17c7245..0000000 --- a/implementation/16-caching/templates/partials/foot.html +++ /dev/null @@ -1,3 +0,0 @@ -
- - \ No newline at end of file diff --git a/implementation/16-caching/templates/partials/head.html b/implementation/16-caching/templates/partials/head.html deleted file mode 100644 index 421d387..0000000 --- a/implementation/16-caching/templates/partials/head.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - No Framework: {{title}} - - - - - -
diff --git a/implementation/16-data-repository/.php-cs-fixer.php b/implementation/16-data-repository/.php-cs-fixer.php deleted file mode 100644 index 705a7d7..0000000 --- a/implementation/16-data-repository/.php-cs-fixer.php +++ /dev/null @@ -1,38 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config' - ]) - ); \ No newline at end of file diff --git a/implementation/16-data-repository/.phpcs.xml.dist b/implementation/16-data-repository/.phpcs.xml.dist deleted file mode 100644 index 3b433f6..0000000 --- a/implementation/16-data-repository/.phpcs.xml.dist +++ /dev/null @@ -1,9 +0,0 @@ - - - - - src - config - - - \ No newline at end of file diff --git a/implementation/16-data-repository/cli-config.php b/implementation/16-data-repository/cli-config.php deleted file mode 100644 index fbc6598..0000000 --- a/implementation/16-data-repository/cli-config.php +++ /dev/null @@ -1,13 +0,0 @@ -getContainer(); - -return ConsoleRunner::createHelperSet($container->get(EntityManagerInterface::class)); diff --git a/implementation/16-data-repository/composer.json b/implementation/16-data-repository/composer.json deleted file mode 100644 index b5c7f1a..0000000 --- a/implementation/16-data-repository/composer.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3", - "mustache/mustache": "^2.14", - "psr/http-server-middleware": "^1.0", - "middlewares/trailing-slash": "^2.0", - "middlewares/whoops": "^2.0", - "erusev/parsedown": "^1.7", - "league/commonmark": "^2.2" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require-dev": { - "phpstan/phpstan": "^1.5", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0", - "squizlabs/php_codesniffer": "^3.6", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1", - "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0" - }, - "config": { - "allow-plugins": { - "phpstan/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "serve": [ - "Composer\\Config::disableProcessTimeout", - "php -S 0.0.0.0:1234 -t public" - ], - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" - } -} diff --git a/implementation/16-data-repository/composer.lock b/implementation/16-data-repository/composer.lock deleted file mode 100644 index a62d9c7..0000000 --- a/implementation/16-data-repository/composer.lock +++ /dev/null @@ -1,2438 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "00acf07ae222f9117a84bce157b99837", - "packages": [ - { - "name": "dflydev/dot-access-data", - "version": "v3.0.1", - "source": { - "type": "git", - "url": "https://github.com/dflydev/dflydev-dot-access-data.git", - "reference": "0992cc19268b259a39e86f296da5f0677841f42c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", - "reference": "0992cc19268b259a39e86f296da5f0677841f42c", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.42", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", - "scrutinizer/ocular": "1.6.0", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^3.14" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Dflydev\\DotAccessData\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dragonfly Development Inc.", - "email": "info@dflydev.com", - "homepage": "http://dflydev.com" - }, - { - "name": "Beau Simensen", - "email": "beau@dflydev.com", - "homepage": "http://beausimensen.com" - }, - { - "name": "Carlos Frutos", - "email": "carlos@kiwing.it", - "homepage": "https://github.com/cfrutos" - }, - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com" - } - ], - "description": "Given a deep data structure, access data by dot notation.", - "homepage": "https://github.com/dflydev/dflydev-dot-access-data", - "keywords": [ - "access", - "data", - "dot", - "notation" - ], - "support": { - "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", - "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" - }, - "time": "2021-08-13T13:06:58+00:00" - }, - { - "name": "erusev/parsedown", - "version": "1.7.4", - "source": { - "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35" - }, - "type": "library", - "autoload": { - "psr-0": { - "Parsedown": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com" - } - ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org", - "keywords": [ - "markdown", - "parser" - ], - "support": { - "issues": "https://github.com/erusev/parsedown/issues", - "source": "https://github.com/erusev/parsedown/tree/1.7.x" - }, - "time": "2019-12-30T22:54:17+00:00" - }, - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.9.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2022-03-29T20:12:16+00:00" - }, - { - "name": "league/commonmark", - "version": "2.2.3", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/commonmark.git", - "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", - "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "league/config": "^1.1.1", - "php": "^7.4 || ^8.0", - "psr/event-dispatcher": "^1.0", - "symfony/deprecation-contracts": "^2.1 || ^3.0", - "symfony/polyfill-php80": "^1.15" - }, - "require-dev": { - "cebe/markdown": "^1.0", - "commonmark/cmark": "0.30.0", - "commonmark/commonmark.js": "0.30.0", - "composer/package-versions-deprecated": "^1.8", - "erusev/parsedown": "^1.0", - "ext-json": "*", - "github/gfm": "0.29.0", - "michelf/php-markdown": "^1.4", - "phpstan/phpstan": "^0.12.88 || ^1.0.0", - "phpunit/phpunit": "^9.5.5", - "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", - "unleashedtech/php-coding-standard": "^3.1", - "vimeo/psalm": "^4.7.3" - }, - "suggest": { - "symfony/yaml": "v2.3+ required if using the Front Matter extension" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.3-dev" - } - }, - "autoload": { - "psr-4": { - "League\\CommonMark\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com", - "role": "Lead Developer" - } - ], - "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", - "homepage": "https://commonmark.thephpleague.com", - "keywords": [ - "commonmark", - "flavored", - "gfm", - "github", - "github-flavored", - "markdown", - "md", - "parser" - ], - "support": { - "docs": "https://commonmark.thephpleague.com/", - "forum": "https://github.com/thephpleague/commonmark/discussions", - "issues": "https://github.com/thephpleague/commonmark/issues", - "rss": "https://github.com/thephpleague/commonmark/releases.atom", - "source": "https://github.com/thephpleague/commonmark" - }, - "funding": [ - { - "url": "https://www.colinodell.com/sponsor", - "type": "custom" - }, - { - "url": "https://www.paypal.me/colinpodell/10.00", - "type": "custom" - }, - { - "url": "https://github.com/colinodell", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/commonmark", - "type": "tidelift" - } - ], - "time": "2022-02-26T21:24:45+00:00" - }, - { - "name": "league/config", - "version": "v1.1.1", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/config.git", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "shasum": "" - }, - "require": { - "dflydev/dot-access-data": "^3.0.1", - "nette/schema": "^1.2", - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.90", - "phpunit/phpunit": "^9.5.5", - "scrutinizer/ocular": "^1.8.1", - "unleashedtech/php-coding-standard": "^3.1", - "vimeo/psalm": "^4.7.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.2-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Config\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com", - "role": "Lead Developer" - } - ], - "description": "Define configuration arrays with strict schemas and access values with dot notation", - "homepage": "https://config.thephpleague.com", - "keywords": [ - "array", - "config", - "configuration", - "dot", - "dot-access", - "nested", - "schema" - ], - "support": { - "docs": "https://config.thephpleague.com/", - "issues": "https://github.com/thephpleague/config/issues", - "rss": "https://github.com/thephpleague/config/releases.atom", - "source": "https://github.com/thephpleague/config" - }, - "funding": [ - { - "url": "https://www.colinodell.com/sponsor", - "type": "custom" - }, - { - "url": "https://www.paypal.me/colinpodell/10.00", - "type": "custom" - }, - { - "url": "https://github.com/colinodell", - "type": "github" - } - ], - "time": "2021-08-14T12:15:32+00:00" - }, - { - "name": "middlewares/trailing-slash", - "version": "v2.0.1", - "source": { - "type": "git", - "url": "https://github.com/middlewares/trailing-slash.git", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", - "shasum": "" - }, - "require": { - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to normalize the trailing slash of the uri path", - "homepage": "https://github.com/middlewares/trailing-slash", - "keywords": [ - "http", - "middleware", - "normalize", - "path", - "psr-15", - "psr-7", - "slash" - ], - "support": { - "issues": "https://github.com/middlewares/trailing-slash/issues", - "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" - }, - "time": "2020-12-02T00:06:55+00:00" - }, - { - "name": "middlewares/utils", - "version": "v3.3.0", - "source": { - "type": "git", - "url": "https://github.com/middlewares/utils.git", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^v2.16", - "guzzlehttp/psr7": "^2.0", - "laminas/laminas-diactoros": "^2.4", - "nyholm/psr7": "^1.0", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "slim/psr7": "^1.4", - "squizlabs/php_codesniffer": "^3.5", - "sunrise/http-message": "^1.0", - "sunrise/http-server-request": "^1.0", - "sunrise/stream": "^1.0.15", - "sunrise/uri": "^1.0.15" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\Utils\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Common utils for PSR-15 middleware packages", - "homepage": "https://github.com/middlewares/utils", - "keywords": [ - "PSR-11", - "http", - "middleware", - "psr-15", - "psr-17", - "psr-7" - ], - "support": { - "issues": "https://github.com/middlewares/utils/issues", - "source": "https://github.com/middlewares/utils/tree/v3.3.0" - }, - "time": "2021-07-04T17:56:23+00:00" - }, - { - "name": "middlewares/whoops", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/middlewares/whoops.git", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", - "shasum": "" - }, - "require": { - "filp/whoops": "^2.5", - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "eloquent/phony-phpunit": "^5.0 || ^7.0", - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to use Whoops as error handler", - "homepage": "https://github.com/middlewares/whoops", - "keywords": [ - "error", - "http", - "middleware", - "psr-15", - "psr-7", - "server", - "whoops" - ], - "support": { - "issues": "https://github.com/middlewares/whoops/issues", - "source": "https://github.com/middlewares/whoops/tree/v2.0.2" - }, - "time": "2022-01-27T20:31:30+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nette/schema", - "version": "v1.2.2", - "source": { - "type": "git", - "url": "https://github.com/nette/schema.git", - "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", - "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", - "shasum": "" - }, - "require": { - "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": ">=7.1 <8.2" - }, - "require-dev": { - "nette/tester": "^2.3 || ^2.4", - "phpstan/phpstan-nette": "^0.12", - "tracy/tracy": "^2.7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "📐 Nette Schema: validating data structures against a given Schema.", - "homepage": "https://nette.org", - "keywords": [ - "config", - "nette" - ], - "support": { - "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.2" - }, - "time": "2021-10-15T11:40:02+00:00" - }, - { - "name": "nette/utils", - "version": "v3.2.7", - "source": { - "type": "git", - "url": "https://github.com/nette/utils.git", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", - "shasum": "" - }, - "require": { - "php": ">=7.2 <8.2" - }, - "conflict": { - "nette/di": "<3.0.6" - }, - "require-dev": { - "nette/tester": "~2.0", - "phpstan/phpstan": "^1.0", - "tracy/tracy": "^2.3" - }, - "suggest": { - "ext-gd": "to use Image", - "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", - "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", - "ext-json": "to use Nette\\Utils\\Json", - "ext-mbstring": "to use Strings::lower() etc...", - "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", - "ext-xml": "to use Strings::length() etc. when mbstring is not available" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", - "homepage": "https://nette.org", - "keywords": [ - "array", - "core", - "datetime", - "images", - "json", - "nette", - "paginator", - "password", - "slugify", - "string", - "unicode", - "utf-8", - "utility", - "validation" - ], - "support": { - "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.2.7" - }, - "time": "2022-01-24T11:29:14+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/http-server-middleware", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-middleware.git", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0", - "psr/http-server-handler": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side middleware", - "keywords": [ - "http", - "http-interop", - "middleware", - "psr", - "psr-15", - "psr-7", - "request", - "response" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-middleware/issues", - "source": "https://github.com/php-fig/http-server-middleware/tree/master" - }, - "time": "2018-10-30T17:12:04+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.0.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "shasum": "" - }, - "require": { - "php": ">=8.0.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:55:41+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-04T08:16:47+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "doctrine/coding-standard", - "version": "8.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/coding-standard.git", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "slevomat/coding-standard": "^6.4.1", - "squizlabs/php_codesniffer": "^3.5.8" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Steve Müller", - "email": "st.mueller@dzh-online.de" - } - ], - "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", - "keywords": [ - "checks", - "code", - "coding", - "cs", - "doctrine", - "rules", - "sniffer", - "sniffs", - "standard", - "style" - ], - "support": { - "issues": "https://github.com/doctrine/coding-standard/issues", - "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" - }, - "time": "2021-04-03T10:54:55+00:00" - }, - { - "name": "mnapoli/hard-mode", - "version": "0.3.0", - "source": { - "type": "git", - "url": "https://github.com/mnapoli/hard-mode.git", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", - "shasum": "" - }, - "require": { - "doctrine/coding-standard": "^8.0" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Hard mode for PHP", - "support": { - "issues": "https://github.com/mnapoli/hard-mode/issues", - "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" - }, - "time": "2020-10-12T07:54:37+00:00" - }, - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.9", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.26", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/master" - }, - "time": "2020-08-03T20:32:43+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.5.4", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", - "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.4" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-04-03T12:39:00+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^1.2.0" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" - }, - "time": "2021-11-18T09:30:29+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "6.4.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpstan/phpstan": "0.12.48", - "phpstan/phpstan-deprecation-rules": "0.12.5", - "phpstan/phpstan-phpunit": "0.12.16", - "phpstan/phpstan-strict-rules": "0.12.5", - "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2020-10-05T12:39:37+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.3.0" -} diff --git a/implementation/16-data-repository/config/dependencies.php b/implementation/16-data-repository/config/dependencies.php deleted file mode 100644 index 0040933..0000000 --- a/implementation/16-data-repository/config/dependencies.php +++ /dev/null @@ -1,55 +0,0 @@ - fn (SystemClockNow $n) => $n, - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, - Emitter::class => fn (BasicEmitter $e) => $e, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, - CacheInterface::class => fn (FilesystemAdapter $a) => $a, - MarkdownParser::class => fn (ParsedownParser $p) => $p, - MarkdownPageRepo::class => fn (FileSystemMarkdownPageRepo $r) => $r, - - // Factories - ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), - Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), -]; diff --git a/implementation/16-data-repository/config/middlewares.php b/implementation/16-data-repository/config/middlewares.php deleted file mode 100644 index 71dd461..0000000 --- a/implementation/16-data-repository/config/middlewares.php +++ /dev/null @@ -1,11 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/page', [Page::class, 'list']); - $r->addRoute('GET', '/page/{page}', [Page::class, 'show']); - $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); - $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -}; diff --git a/implementation/16-data-repository/config/settings.php b/implementation/16-data-repository/config/settings.php deleted file mode 100644 index c654565..0000000 --- a/implementation/16-data-repository/config/settings.php +++ /dev/null @@ -1,12 +0,0 @@ ->](02-composer.md) - -### Front Controller - -A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. - -To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. - -A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. - -The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. - -But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. - -So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. - -Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: - -```php ->](02-composer.md) diff --git a/implementation/16-data-repository/data/pages/02-composer.md b/implementation/16-data-repository/data/pages/02-composer.md deleted file mode 100644 index a25a4a8..0000000 --- a/implementation/16-data-repository/data/pages/02-composer.md +++ /dev/null @@ -1,75 +0,0 @@ -[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) - -### Composer - -[Composer](https://getcomposer.org/) is a dependency manager for PHP. - -Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do -something. With Composer, you can install third-party libraries for your application. - -If you don't have Composer installed already, head over to the website and install it. You can find Composer packages -for your project on [Packagist](https://packagist.org/). - -Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will -be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. - -Add the following content to the file: - -```json -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubiana", - "email": "lubiana@hannover.ccc.de" - } - ] -} -``` - -In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use -whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. -Just replace it with your namespace in your own code. - -I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. - -As the Bootstrap.php file is placed in that directory we should -add the namespace to the File as well. Here is my current Bootstrap.php -as a reference: - -```php ->](03-error-handler.md) diff --git a/implementation/16-data-repository/data/pages/03-error-handler.md b/implementation/16-data-repository/data/pages/03-error-handler.md deleted file mode 100644 index 60465d0..0000000 --- a/implementation/16-data-repository/data/pages/03-error-handler.md +++ /dev/null @@ -1,79 +0,0 @@ -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) - -### Error Handler - -An error handler allows you to customize what happens if your code results in an error. - -A nice error page with a lot of information for debugging goes a long way during development. So the first package -for your application will take care of that. - -I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. -If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, -you have total control over your project. - -An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) - -To install a new package, open up your `composer.json` and add the package to the require part. It should now look -like this: - -```php -"require": { - "php": ">=8.1.0", - "filp/whoops": "^2.14" -}, -``` - -Now run `composer update` in your console and it will be installed. - -Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, -i that case composer automatically installs the package and updates your composer.json-file. - -But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, -ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you -only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. - -**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message -can help someone to gain access to your system. Always show a user friendly error page instead and send an email to -yourself, write to a log or something similar. So only you can see the errors in the production environment. - -For development that does not make sense though -- you want a nice error page. The solution is to have an environment -switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in -case no environment has been set. - -Then after the error handler registration, throw an `Exception` to test if everything is working correctly. -Your `Bootstrap.php` should now look similar to this: - -```php -pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (\Throwable $e) { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -throw new \Exception("Ooooopsie"); - -``` - -You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until -you get it working. Now would also be a good time for another commit. - - -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/implementation/16-data-repository/data/pages/04-development-helpers.md b/implementation/16-data-repository/data/pages/04-development-helpers.md deleted file mode 100644 index 9505284..0000000 --- a/implementation/16-data-repository/data/pages/04-development-helpers.md +++ /dev/null @@ -1,260 +0,0 @@ -[<< previous](03-error-handler.md) | [next >>](05-http.md) - -### Development Helpers - -I have added some more helpers to my composer.json that help me with development. As these are scripts and programms -used only for development they should not be used in a production environment. Composer has a specific sections in its -file called "dev-dependencies", everything that is required in this section does not get installen in production. - -Let's install our dev-helpers and i will explain them one by one: -`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` - -#### Static Code Analysis with phpstan - -Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or -create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements -that are not (or not always) available, if-statements that always are true and a lot of other stuff. - -A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. - -```php -/** - * @param \DateTime $date - * @return void - */ -function printDate($date) { - $date->format('Y-m-d H:i:s'); -} - -printDate('now'); -``` -if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` - -It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has -no use, and secondly, that we are calling the function with a string instead of a datetime object. - -```shell -1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - ------ --------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ --------------------------------------------------------------------------------------------- -30 Call to method DateTime::format() on a separate line has no effect. -33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. - ------ --------------------------------------------------------------------------------------------- -``` - -The second error is something that "declare strict-types" already catches for us, but the first error is something that -we usually would not discover easily without speccially looking for this errortype. - -We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and -path everytime we want to check our code for errors: - -```yaml -parameters: - level: max - paths: - - src -``` -now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project - -With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us -some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. - -```shell -composer require --dev phpstan/extension-installer -composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules -``` - -During the first install you need to allow the extension installer to actually install the extension. The second command -installs some more strict rulesets and activates them in phpstan. - -If we now rerun phpstan it already tells us about some errors we have made: - -``` - ------ ----------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ ----------------------------------------------------------------------------------------------- -10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider - using long ternary. -25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: - http://bit.ly/subtypeexception -26 Unreachable statement - code above always terminates. - ------ ----------------------------------------------------------------------------------------------- -``` - -The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove -that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific -problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working -on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages -everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we -are adding to our code. - -In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the -phpstan.neon file and run phpstan with the -'--generate-baseline' option: - -```yaml -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` -```shell -[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline -Note: Using configuration file /home/vagrant/app/phpstan.neon. - 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - - - [OK] Baseline generated with 1 error. - - -``` - -you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) - -#### PHP-CS-Fixer - -Another great tool is the php-cs-fixer, which just applies a specific style to your code. - -when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current -directory. - -You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) - -personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup -a configuration file that i use in all my projects .php-cs-fixer.php: - -```php -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - ]) - ); -``` - -#### PHP Codesniffer - -The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra -rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. - -it provides the `phpcs` command to check for violations and the `phpcbf` command to actually fix most of the violations. - -Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have -guessed we are adding some extra rules. - -Lets install the ruleset with composer -```shell -composer require --dev mnapoli/hard-mode -``` - -and add a configuration file to actually use it '.phpcs.xml.dist' -```xml - - - - - src - - - -``` - -running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. - -``` -[vagrant@archlinux app]$ ./vendor/bin/phpcs - -FILE: src/Bootstrap.php ----------------------------------------------------------------------------------------------------- -FOUND 4 ERRORS AFFECTING 4 LINES ----------------------------------------------------------------------------------------------------- - 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. - 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead ----------------------------------------------------------------------------------------------------- -PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY ----------------------------------------------------------------------------------------------------- - -Time: 639ms; Memory: 10MB -``` - -You can then use `./vendor/bin/phpcbf` to try to fix them. - - -#### Symfony Var-Dumper - -another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small -functions. - -dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects -or arrays. - -dd() on the other hand is a function that dumps its parameters and then exits the php-script. - -you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. - -#### Composer scripts - -now we have a few commands that are available on the command line. i personally do not like to type complex commands -with lots of parameters by hand all the time, so i added a few lines to my composer.json: - -```json -"scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" -}, -``` - -that way i can just type "composer" followed by the command name in the root of my project. if i want to start the -php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the -time. - -You could also configure PhpStorm to automatically run these commands in the background and highlight the violations -directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my -flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. - -My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before -commiting and pushing the code. - -[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/implementation/16-data-repository/data/pages/05-http.md b/implementation/16-data-repository/data/pages/05-http.md deleted file mode 100644 index 6166214..0000000 --- a/implementation/16-data-repository/data/pages/05-http.md +++ /dev/null @@ -1,124 +0,0 @@ -[<< previous](04-development-helpers.md) | [next >>](06-router.md) - -### HTTP - -PHP already has a few things built in to make working with HTTP easier. For example there are the -[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. - -These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, -if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, -then you will want a class with a nice object-oriented interface that you can use in your application instead. - -Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The -standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php -projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. - -As this is a widely adopted standard there are already several implementations available for us to use. I will choose -the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. - -Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a -[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. - -Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use -that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding -the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). - - -to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit -enter - -Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): - -```php -$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); -$response = new \Laminas\Diactoros\Response; -$response->getBody()->write('Hello World! '); -$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); -``` - -This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. - -In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the -write()-Method on that object. - - -To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: - -```php -echo $response->getBody(); -``` - -This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only -stores data. - -You can play around with the other methods of the Request object and take a look at its content with the dd() function. - -```php -dd($response) -``` - -Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot -be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which -creates a copy of the request object with the changed property and returns that clone: - -```php -$response = $response->withStatus(200); -$response = $response->withAddedHeader('Content-type', 'application/json'); -``` - -If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been -defined this way. - -But if you have been keeping attention you might argue that the following line should not work if the request object is -immutable. - -```php -$response->getBody()->write('Hello World!'); -``` - -The response-body implements a stream interface which is immutable for some reasons that are described in the -[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be -aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in -the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. -I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content -to a response object. - -```php -$body = $response->getBody(); -$body->write('Hello World!'); -$response = $response->withBody($body); -``` - -Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our -output-logic a little bit more. Replace the line that echos the response-body with the following: - -```php -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); -``` - -This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a -webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. - -Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. - -Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter - - -[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/implementation/16-data-repository/data/pages/06-router.md b/implementation/16-data-repository/data/pages/06-router.md deleted file mode 100644 index 6c39ae5..0000000 --- a/implementation/16-data-repository/data/pages/06-router.md +++ /dev/null @@ -1,101 +0,0 @@ -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) - -### Router - -A router dispatches to different handlers depending on rules that you have set up. - -With your current setup it does not matter what URL is used to access the application, it will always result in the same -response. So let's fix that now. - -I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own -favorite package. - -Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) - -By now you know how to install Composer packages, so I will leave that to you. - -Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. - -```php -$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -switch ($routeInfo[0]) { - case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: - $response = (new \Laminas\Diactoros\Response)->withStatus(405); - $response->getBody()->write('Method not allowed'); - $response = $response->withStatus(405); - break; - case \FastRoute\Dispatcher::FOUND: - $handler = $routeInfo[1]; - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - /** @var \Psr\Http\Message\ResponseInterface $response */ - $response = call_user_func($handler, $request); - break; - case \FastRoute\Dispatcher::NOT_FOUND: - default: - $response = (new \Laminas\Diactoros\Response)->withStatus(404); - $response->getBody()->write('Not Found!'); - break; -} -``` - -In the first part of the code, you are registering the available routes for your application. In the second part, the -dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, -we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. -If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. - -This setup might work for really small applications, but once you start adding a few routes your bootstrap file will -quickly get cluttered. So let's move them out into a separate file. - -Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; - -```php -addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}; -``` - -Now let's rewrite the route dispatcher part to use the `Routes.php` file. - -```php -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); -``` - -This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. - -Of course we now need to add the 'config' folder to the configuration files of our -devhelpers so that they can scan that directory as well. - -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/implementation/16-data-repository/data/pages/07-dispatching-to-a-class.md b/implementation/16-data-repository/data/pages/07-dispatching-to-a-class.md deleted file mode 100644 index 0c961a4..0000000 --- a/implementation/16-data-repository/data/pages/07-dispatching-to-a-class.md +++ /dev/null @@ -1,137 +0,0 @@ -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) - -### Dispatching to a Class - -In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). -MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to -learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) -and the followup posts. - -So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). - -We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other -common names are 'Controllers' or 'Actions'. - -Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. -In there, create a `Hello.php` file. - -```php -getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - } -} -``` - -You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) -that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is -fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement -it in some other parts of our application as well. In order to use that Interface we have to require it with composer: -'composer require psr/http-server-handler'. - -The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. -At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. - -Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: - -```php -return function(\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); - $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); -}; -``` - -Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared -the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing -the response to the one we defined for the second route. - -To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: - -```php -case \FastRoute\Dispatcher::FOUND: - $handler = new $routeInfo[1]; - if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { - throw new \Exception('Invalid Requesthandler'); - } - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - assert($response instanceof \Psr\Http\Message\ResponseInterface) - break; -``` - -So instead of just calling a method you are now instantiating an object and then calling the method on it. - -Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. - -And of course don't forget to commit your changes. - -Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still -generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for -example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still -have our whoopsie error-handler but i like to be more explicit in my control flow. - -In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new -Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and -InternalServerError. All three should extend phps Base Exception class. - -Here is my NotFound.php for example. - -```php - $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - break; - case Dispatcher::METHOD_NOT_ALLOWED: - throw new MethodNotAllowed; - - case Dispatcher::NOT_FOUND: - default: - throw new NotFound; - } -} catch (MethodNotAllowed) { - $response = (new Response)->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = (new Response)->withStatus(404); - $response->getBody()->write('Not Found'); -} catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); -} -``` - -Check if our code still works, try to trigger some errors, run phpstan and the fix command -and don't forget to commit your changes. - -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/implementation/16-data-repository/data/pages/08-inversion-of-control.md b/implementation/16-data-repository/data/pages/08-inversion-of-control.md deleted file mode 100644 index 21f4f23..0000000 --- a/implementation/16-data-repository/data/pages/08-inversion-of-control.md +++ /dev/null @@ -1,54 +0,0 @@ -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) - -### Inversion of Control - -In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we -want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then -we would need to edit every one of our request handlers to call a different constructor of the class. - -The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that -instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done -with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). - -If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is -implemented, it will make sense. - -Change your `Hello` action to the following: - -```php -getAttribute('name', 'Stranger'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: - -```php -$handler = new $className($response); -``` - -Of course we need to also update all the other handlers. - -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/implementation/16-data-repository/data/pages/09-dependency-injector.md b/implementation/16-data-repository/data/pages/09-dependency-injector.md deleted file mode 100644 index 7f7c6a2..0000000 --- a/implementation/16-data-repository/data/pages/09-dependency-injector.md +++ /dev/null @@ -1,213 +0,0 @@ -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) - -### Dependency Injector - -A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when -the class is instantiated. - -Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work -with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look -for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). - -I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) -out of the box. - -After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: - -```php -addDefinitions([ - \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), - \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), -]); - -return $builder->build(); -``` - -In this file we create a containerbuilder, add some definitions to it and return the container. -As the container supports autowiring we only need to define services where we want to use a specific implementation of -an interface. - -In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to -tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second -exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. - -Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), -as we will use that extensively. - -Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally -to create our Handler-Object - -```php -$container = require __DIR__ . '/../config/dependencies.php'; -assert($container instanceof \Psr\Container\ContainerInterface); - -$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); -assert($request instanceof \Psr\Http\Message\ServerRequestInterface); -``` - -The other part that has to be changed is the dispatching of the route. Before you had the following code: - -```php -$className = $routeInfo[1]; -$handler = new $className($response); -assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Change that to the following: - -```php -/** @var RequestHandlerInterface $handler */ -$className = $routeInfo[1]; -$handler = $container->get($className); -assert($handler instanceof RequestHandlerInterface); -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Make sure to use the container fetch the response object in the catch blocks as well: - -```php -} catch (MethodNotAllowed) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(404); - $response->getBody()->write('Not Found'); -} -``` - -Now all your controller constructor dependencies will be automatically resolved with PHP-DI. - -We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons -in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call -`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we -want to work with a different date in a test. - -Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation -that creates us a DateTimeImmutable object with the current date and time. - -src/Service/Time/Now.php: -```php -namespace Lubian\NoFramework\Service\Time; - -interface Now -{ - public function __invoke(): \DateTimeImmutable; -} -``` -src/Service/Time/SystemClockNow.php: -```php -namespace Lubian\NoFramework\Service\Time; - -final class SystemClockNow implements Now -{ - - public function __invoke(): \DateTimeImmutable - { - return new \DateTimeImmutable; - } -} -``` -If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and -update the handle-method to use the new class property: - -```php -getAttribute('name', 'Stranger'); - $nowAsString = ($this->now)()->format('H:i:s'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowAsString); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI -automatically figures out what classes are requested in the constructor and tries to create the objects needed. - -But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred -S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: - -```php - public function __construct( - private ResponseInterface $response, - private Now $now, - ) -``` - -When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation -should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: - -```php -\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), -``` - -we could also use the PHP-DI create method to delegate the object creation to the container implementation: -```php -\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), -``` - -this way the container can try to resolve any dependencies that the class might have internally, but prefer the other -method because we are not depending on this specific dependency injection implementation. - -Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are -requesting the Hello action. - -If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As -we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way -we wont get any warnings for this particular issue, but for any other issues we add to our code. - -Update the phpstan.neon file to include a "baseline" file: - -``` -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` - -if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and -ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just -'baseline' - -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/implementation/16-data-repository/data/pages/10-invoker.md b/implementation/16-data-repository/data/pages/10-invoker.md deleted file mode 100644 index 3033fae..0000000 --- a/implementation/16-data-repository/data/pages/10-invoker.md +++ /dev/null @@ -1,102 +0,0 @@ -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) - -### Invoker - -Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the -one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long -with some services and not the whole Requestobject with all its various properties. - -If we take our Hello action for example we only need a response object, the time service and the 'name' information from -the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named -the class hello and it would be redundant to also call the the method hello. So an updated version of that class could -look like this: - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - string $name = 'Stranger', - ): ResponseInterface - { - $body = $this->response->getBody(); - $nowString = $now->get()->format('H:i:s'); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowString); - return $response - ->withBody($body) - ->withStatus(200); - } -} -``` - -It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short -closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the -rootpath of our application yet. - -```php -$r->addRoute('GET', '/hello[/{name}]', Hello::class); -$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); -$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -``` - -In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the -route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that -class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what -arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router -if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple -callable we would need to manually use reflection as well to resolve all the arguments. - -But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) -which handles all of that for us. - -After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo -switch section in the Bootstrap.php file to use the container->call() method: - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2]; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$response = $container->call($handler, $args); -``` - -Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. - -But by now you should know that I do not like to depend on specific implementations and the call method is not defined in -the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container -or any other implementation. - -Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific -container implementation so we could use it with any container that implements the ContainerInterface. And best of all -the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that -we could implement if we ever want to write our own implementation or we could write an adapter that uses a different -class that solves the same problem. - -But for now we are using the solution provided by PHP-DI. -So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2] ?? []; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$invoker = $container->get(InvokerInterface::class); -assert($invoker instanceof InvokerInterface); -$response = $invoker->call($handler, $args); -assert($response instanceof ResponseInterface); -``` - -Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) -by php, and even some more. - -But let us move on to something more fun and add some templating functionality to our application as we are trying to build -a website in the end. - -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/implementation/16-data-repository/data/pages/11-templating.md b/implementation/16-data-repository/data/pages/11-templating.md deleted file mode 100644 index 7bfe1aa..0000000 --- a/implementation/16-data-repository/data/pages/11-templating.md +++ /dev/null @@ -1,236 +0,0 @@ -[<< previous](10-invoker.md) | [next >>](12-configuration.md) - -### Templating - -A template engine is not necessary with PHP because the language itself can take care of that. But it can make things -like escaping values easier. They also make it easier to draw a clear line between your application logic and the -template files which should only put your variables into the HTML code. - -A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please -also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. -Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. - -For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install -that package before you continue (`composer require mustache/mustache`). - -Another well known alternative would be [Twig](http://twig.sensiolabs.org/). - -Now please go and have a look at the source code of the -[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class -does not implement an interface. - -You could just type hint against the concrete class. But the problem with this approach is that you create tight -coupling. - -In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the -implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want -to add functionality to the engine. You can't do that without going back and changing all your code that is tightly -coupled. - -What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need -another implementation, you just implement that interface in your new class and inject the new class instead. - -Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). -This sounds a lot more complicated than it is, so just follow along. - -First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). -This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. -A class can implement multiple interfaces if necessary. - -So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a -new folder in your `src/` folder with the name `Template` where you can put all the template related things. - -In there create a new interface `Renderer.php` that looks like this: - -```php - $data */ - public function render(string $template, array $data = []) : string; -} -``` - -Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file -`MustacheRenderer.php` with the following content: - -```php -engine->render($template, $data); - } -} -``` - -As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple -and only fulfills the interface. - -Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know -which implementation he has to inject when you hint for the interface. Add this line: - -```php -[ - ... - \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) - ->constructor(new Mustache_Engine), -] -``` - -Now update the Hello.php class to require an implementation of our renderer interface -and use that to render a string using mustache syntax. - - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - Renderer $renderer, - string $name = 'Stranger', - ): ResponseInterface { - $body = $response->getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render( - 'Hello {{name}}, the time is {{now}}!', - $data, - ); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} -``` - -Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. -But what we want is template files, so let's go back and change that. - -To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the -`dependencies.php` file and add the following code: - -```php -[ - ... - Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), - Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), -] -``` - -We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. -Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have -to rename all the template files. - -To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the -dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader -in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object -we can then use it in the factory. - -In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: - -``` -

Hello World

-Hello {{ name }} -``` - -Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` - -Navigate to the hello page in your browser to make sure everything works. - -One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies -file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration -values. - -Lets create a 'Settings' class in our './src' Folder: - -```php -addDefinitions([ - Settings::class => fn () => require __DIR__ '/settings.php', - ResponseInterface::class => create(Response::class), - ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), - Renderer::class => fn (ME $me) => new Mustache($me), - MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), -]); - -return $builder->build(); -``` - - - -And as always, don't forget to commit your changes. - - -[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/implementation/16-data-repository/data/pages/12-configuration.md b/implementation/16-data-repository/data/pages/12-configuration.md deleted file mode 100644 index a44dfd5..0000000 --- a/implementation/16-data-repository/data/pages/12-configuration.md +++ /dev/null @@ -1,201 +0,0 @@ -[<< previous](11-templating.md) | [next >>](13-refactoring.md) - -### Configuration - -In the last chapter we added some more definitions to our dependencies.php in that definitions -we needed to pass quite a few configuration settings and filesystem strings to the constructors -of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. - -As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. - -As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. - -Lets create a 'Settings' class in our './src' Folder: - -```php -filePath; - } -} -``` - -If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files -and craft a settings object from them. - -As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another -factory for our Container because everyone should have a Friend :) - -```php -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn () => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} -``` - -For this to work we need to change our dependencies.php file to just return the array of definitions: -And here we can instantly use the Settings object to create our template engine. - -```php - fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), -]; -``` - -Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: -require __DIR__ . '/../vendor/autoload.php'; - -```php -... -error_reporting(E_ALL); - -$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); -$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); -... -``` - -Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. - -[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/implementation/16-data-repository/data/pages/13-refactoring.md b/implementation/16-data-repository/data/pages/13-refactoring.md deleted file mode 100644 index 067e168..0000000 --- a/implementation/16-data-repository/data/pages/13-refactoring.md +++ /dev/null @@ -1,377 +0,0 @@ -[<< previous](12-configuration.md) | [next >>](14-middleware.md) - -### Refactoring - -By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no -reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. -After all the bootstrap file should just set up the classes needed for the handling logic and execute them. - -At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. -As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the -method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non -static and extracted an interface. - -'./src/Http/Emitter.php' -```php -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - echo $response->getBody(); - } -} -``` -After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following -code in the Bootstrap.php to emit the response: - -```php -/** @var Emitter $emitter */ -$emitter = $container->get(Emitter::class); -$emitter->emit($response); -``` - -If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily -write an adapter that implements your emitter interface and wraps that more advanced emitter - -Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and -calling the routerhandler that in the passes the request to a function and gets the response. - -For this to steps to be seperated we are going to create two more classes: -1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object -2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the - requestobject, fetches the correct object from the container and calls it to create a response. - -Lets create the HandlerInterface first: - -```php -getAttribute($this->routeAttributeName, false); - assert($handler !== false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} - -``` - -We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. -The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler -as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in -the next chapter. - -```php -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} -``` - -Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. -```php -[ - '...', - Emitter::class => fn (BasicEmitter $e) => $e, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, -], -``` - -And then we can update our Bootstrap.php to fetch all the services and let them handle the request. - -```php -... -$routeMiddleWare = $container->get(MiddlewareInterface::class); -assert($routeMiddleWare instanceof MiddlewareInterface); -$handler = $container->get(RoutedRequestHandler::class); -assert($handler instanceof RequestHandlerInterface); -$emitter = $container->get(Emitter::class); -assert($emitter instanceof Emitter); - -$request = $container->get(ServerRequestInterface::class); -assert($request instanceof ServerRequestInterface); - -$response = $routeMiddleWare->process($request, $handler); -$emitter->emit($response); -``` -Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of -code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). - -So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. - -I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class -should require to function properly. - -* A RequestFactory - We want our Kernel to be able to build the request itself -* An Emitter - Without an Emitter we will not be able to send the response to the client -* RouteMiddleware - To decore the request with the correct handler for the requested route -* RequestHandler - To delegate the request to the correct funtion that creates the response - -As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface -to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our -interface: - -```php -factory::fromGlobals(); - } - - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} -``` - -For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: - -```php -routeMiddleware->process($request, $this->handler); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} - -``` - -We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines - -```php -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); -``` - -You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking -at the Whoops output and adding the needed definitions to the dependencies.php file. - -And as always, don't forget to commit your changes. - -[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/implementation/16-data-repository/data/pages/14-middleware.md b/implementation/16-data-repository/data/pages/14-middleware.md deleted file mode 100644 index e698327..0000000 --- a/implementation/16-data-repository/data/pages/14-middleware.md +++ /dev/null @@ -1,298 +0,0 @@ -[<< previous](12-refactoring.md) | [next >>](14-invoker.md) - -### Middleware - -In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain -a bit more about what this interface does and why it is used in many applications. - -The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets -passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all -the middlewars to the client/emitter. - -So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the -response after it gets created by our handlers. - -So lets take a look at the middleware and the requesthandler interfaces - -```php -interface MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; -} - -interface RequestHandlerInterface -{ - public function handle(ServerRequestInterface $request): ResponseInterface; -} -``` - -The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a -requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the -response. - -But the middleware could just ignore the handler and produce a response on its own as the interface just requires us -to produce a response. - -A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users -that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the -database. - -In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates -the request with an 'isAuthenticated' attribute. - -If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that -response is not already cached, than we let the handler create the response and store that in the cache for a few -seconds - -```php -interface CacheInterface -{ - public function get(string $key, callable $resolver, int $ttl): mixed; -} -``` - -The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one -defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses -the callable to produce the value and stores it for the time specified in ttl. - -so lets write our caching middleware: - -```php -final class CachingMiddleware implements MiddlewareInterface -{ - public function __construct(private CacheInterface $cache){} - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { - $key = $request->getUri()->getPath(); - return $this->cache->get($key, fn() => $handler->handle($request), 10); - } - return $handler->handle($request); - } -} -``` - -we can also modify the response after it has been created by our application, for example we could implement a gzip -middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser -know that our application is used to serve dank memes: - -```php -final class DankMemeMiddleware implements MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - $response = $handler->handle($request); - return $response->withAddedHeader('Meme', 'Dank'); - } -} -``` - -but for our application we are going to just add two external middlewares: - -* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. -* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware - -```bash -composer require middlewares/trailing-slash -composer require middlewares/whoops -``` - -The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the -application as well as the middleware stack. - -Our desired request -> response flow looks something like this: - - Client - | ^ - v | - Kernel - | ^ - v | - Whoops Middleware - | ^ - v | - TrailingSlash - | ^ - v | - Routing - | ^ - v | - ContainerResolver - | ^ - v | - Controller/Action - -As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every -middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. - -```php -interface Pipeline -{ - public function dispatch(ServerRequestInterface $request): ResponseInterface; -} -``` - -And our implementation looks something like this: - -```php - $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} -``` - -Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code -that should produce our response. - -In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument -and store that itself as the current tip. - -There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) - -Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline -Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline - -```php -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} -``` - -And configure the container to use the Factory to create the Pipeline: - -```php - ..., - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - ... -``` -And of course a new file called middlewares.php in our config folder: -```php -pipeline->dispatch($request); -} -``` - -Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our -pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a -dependency container, if you want you can do that as well. - -[<< previous](12-refactoring.md) | [next >>](14-invoker.md) diff --git a/implementation/16-data-repository/phpstan-baseline.neon b/implementation/16-data-repository/phpstan-baseline.neon deleted file mode 100644 index 61697a1..0000000 --- a/implementation/16-data-repository/phpstan-baseline.neon +++ /dev/null @@ -1,7 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" - count: 1 - path: src/Http/InvokerRoutedHandler.php - diff --git a/implementation/16-data-repository/phpstan.neon b/implementation/16-data-repository/phpstan.neon deleted file mode 100644 index 2eac45a..0000000 --- a/implementation/16-data-repository/phpstan.neon +++ /dev/null @@ -1,8 +0,0 @@ -includes: - - phpstan-baseline.neon - -parameters: - level: max - paths: - - src - - config \ No newline at end of file diff --git a/implementation/16-data-repository/public/css/spectre-exp.min.css b/implementation/16-data-repository/public/css/spectre-exp.min.css deleted file mode 100644 index d313774..0000000 --- a/implementation/16-data-repository/public/css/spectre-exp.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/implementation/16-data-repository/public/css/spectre-icons.min.css b/implementation/16-data-repository/public/css/spectre-icons.min.css deleted file mode 100644 index 0276f7b..0000000 --- a/implementation/16-data-repository/public/css/spectre-icons.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/implementation/16-data-repository/public/css/spectre.min.css b/implementation/16-data-repository/public/css/spectre.min.css deleted file mode 100644 index 0fe23d9..0000000 --- a/implementation/16-data-repository/public/css/spectre.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/implementation/16-data-repository/public/favicon.ico b/implementation/16-data-repository/public/favicon.ico deleted file mode 100644 index 09499b8b3b3201e0f50088e3ac42e167778d1153..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/16-data-repository/public/index.php b/implementation/16-data-repository/public/index.php deleted file mode 100644 index d93da3a..0000000 --- a/implementation/16-data-repository/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render('hello', $data); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/16-data-repository/src/Action/Other.php b/implementation/16-data-repository/src/Action/Other.php deleted file mode 100644 index da9ceaf..0000000 --- a/implementation/16-data-repository/src/Action/Other.php +++ /dev/null @@ -1,16 +0,0 @@ -parse('This *works* **too!**'); - $response->getBody()->write($html); - return $response->withStatus(200); - } -} diff --git a/implementation/16-data-repository/src/Action/Page.php b/implementation/16-data-repository/src/Action/Page.php deleted file mode 100644 index 4af45f0..0000000 --- a/implementation/16-data-repository/src/Action/Page.php +++ /dev/null @@ -1,60 +0,0 @@ -repo->byName($page); - - // fix the next and previous buttons to work with our routing - $content = preg_replace('/\(\d\d-/m', '(', $page->content); - assert(is_string($content)); - $content = str_replace('.md)', ')', $content); - - $data = [ - 'title' => $page->title, - 'content' => $this->parser->parse($content), - ]; - - $html = $this->renderer->render('page/show', $data); - $this->response->getBody()->write($html); - return $this->response; - } - - public function list(): ResponseInterface - { - $pages = array_map(function (MarkdownPage $page) { - return [ - 'id' => $page->id, - 'title' => $page->content, - ]; - }, $this->repo->all()); - - $html = $this->renderer->render('page/list', ['pages' => $pages]); - $this->response->getBody()->write($html); - return $this->response; - } -} diff --git a/implementation/16-data-repository/src/Bootstrap.php b/implementation/16-data-repository/src/Bootstrap.php deleted file mode 100644 index 3abc2e5..0000000 --- a/implementation/16-data-repository/src/Bootstrap.php +++ /dev/null @@ -1,40 +0,0 @@ -getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); diff --git a/implementation/16-data-repository/src/Exception/InternalServerError.php b/implementation/16-data-repository/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/implementation/16-data-repository/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ -factory::fromGlobals(); - } - - /** - * @param UriInterface|string $uri - * @param array $serverParams - */ - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} diff --git a/implementation/16-data-repository/src/Factory/DoctrineEm.php b/implementation/16-data-repository/src/Factory/DoctrineEm.php deleted file mode 100644 index b0be39b..0000000 --- a/implementation/16-data-repository/src/Factory/DoctrineEm.php +++ /dev/null @@ -1,32 +0,0 @@ -settings->doctrine['devMode']); - - $config->setMetadataDriverImpl( - new AttributeDriver( - $this->settings->doctrine['metadataDirs'] - ) - ); - - return EntityManager::create( - $this->settings->connection, - $config, - ); - } -} diff --git a/implementation/16-data-repository/src/Factory/FileSystemSettingsProvider.php b/implementation/16-data-repository/src/Factory/FileSystemSettingsProvider.php deleted file mode 100644 index f071078..0000000 --- a/implementation/16-data-repository/src/Factory/FileSystemSettingsProvider.php +++ /dev/null @@ -1,22 +0,0 @@ -filePath; - assert($settings instanceof Settings); - return $settings; - } -} diff --git a/implementation/16-data-repository/src/Factory/PipelineProvider.php b/implementation/16-data-repository/src/Factory/PipelineProvider.php deleted file mode 100644 index 77738f8..0000000 --- a/implementation/16-data-repository/src/Factory/PipelineProvider.php +++ /dev/null @@ -1,25 +0,0 @@ -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} diff --git a/implementation/16-data-repository/src/Factory/RequestFactory.php b/implementation/16-data-repository/src/Factory/RequestFactory.php deleted file mode 100644 index 2b17abc..0000000 --- a/implementation/16-data-repository/src/Factory/RequestFactory.php +++ /dev/null @@ -1,11 +0,0 @@ -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = $settings; - $builder->addDefinitions($dependencies); - // $builder->enableCompilation('/tmp'); - return $builder->build(); - } -} diff --git a/implementation/16-data-repository/src/Factory/SettingsProvider.php b/implementation/16-data-repository/src/Factory/SettingsProvider.php deleted file mode 100644 index ce1c5f0..0000000 --- a/implementation/16-data-repository/src/Factory/SettingsProvider.php +++ /dev/null @@ -1,10 +0,0 @@ -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - - echo $response->getBody(); - } -} diff --git a/implementation/16-data-repository/src/Http/ContainerPipeline.php b/implementation/16-data-repository/src/Http/ContainerPipeline.php deleted file mode 100644 index 816cedd..0000000 --- a/implementation/16-data-repository/src/Http/ContainerPipeline.php +++ /dev/null @@ -1,82 +0,0 @@ - $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} diff --git a/implementation/16-data-repository/src/Http/Emitter.php b/implementation/16-data-repository/src/Http/Emitter.php deleted file mode 100644 index ce4c035..0000000 --- a/implementation/16-data-repository/src/Http/Emitter.php +++ /dev/null @@ -1,10 +0,0 @@ -getAttribute($this->routeAttributeName, false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} diff --git a/implementation/16-data-repository/src/Http/Pipeline.php b/implementation/16-data-repository/src/Http/Pipeline.php deleted file mode 100644 index 1a9dcda..0000000 --- a/implementation/16-data-repository/src/Http/Pipeline.php +++ /dev/null @@ -1,11 +0,0 @@ -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} diff --git a/implementation/16-data-repository/src/Http/RoutedRequestHandler.php b/implementation/16-data-repository/src/Http/RoutedRequestHandler.php deleted file mode 100644 index a7407c9..0000000 --- a/implementation/16-data-repository/src/Http/RoutedRequestHandler.php +++ /dev/null @@ -1,10 +0,0 @@ -pipeline->dispatch($request); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} diff --git a/implementation/16-data-repository/src/Model/MarkdownPage.php b/implementation/16-data-repository/src/Model/MarkdownPage.php deleted file mode 100644 index df244fd..0000000 --- a/implementation/16-data-repository/src/Model/MarkdownPage.php +++ /dev/null @@ -1,13 +0,0 @@ -dataPath . '*.md'); - if ($files === false) { - throw new InternalServerError('cannot read pages'); - } - return array_map(function (string $filename) { - $content = file_get_contents($filename); - if ($content === false) { - throw new InternalServerError('cannot read pages'); - } - $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); - return new MarkdownPage( - (int) substr($idAndTitle, 0, 2), - substr($idAndTitle, 3), - $content - ); - }, $files); - } - - public function byName(string $name): MarkdownPage - { - $pages = array_values( - array_filter( - $this->all(), - fn (MarkdownPage $p) => $p->title === $name, - ) - ); - - if (count($pages) !== 1) { - throw new NotFound; - } - - return $pages[0]; - } -} diff --git a/implementation/16-data-repository/src/Repository/MarkdownPageRepo.php b/implementation/16-data-repository/src/Repository/MarkdownPageRepo.php deleted file mode 100644 index 0792d32..0000000 --- a/implementation/16-data-repository/src/Repository/MarkdownPageRepo.php +++ /dev/null @@ -1,15 +0,0 @@ -engine->render($template, $data); - } -} diff --git a/implementation/16-data-repository/src/Template/ParsedownParser.php b/implementation/16-data-repository/src/Template/ParsedownParser.php deleted file mode 100644 index 2ffd287..0000000 --- a/implementation/16-data-repository/src/Template/ParsedownParser.php +++ /dev/null @@ -1,17 +0,0 @@ -parser->parse($markdown); - } -} diff --git a/implementation/16-data-repository/src/Template/Renderer.php b/implementation/16-data-repository/src/Template/Renderer.php deleted file mode 100644 index ff916ed..0000000 --- a/implementation/16-data-repository/src/Template/Renderer.php +++ /dev/null @@ -1,11 +0,0 @@ - $data - */ - public function render(string $template, array $data = []): string; -} diff --git a/implementation/16-data-repository/templates/hello.html b/implementation/16-data-repository/templates/hello.html deleted file mode 100644 index 15a4cd2..0000000 --- a/implementation/16-data-repository/templates/hello.html +++ /dev/null @@ -1,6 +0,0 @@ -{{> partials/head }} -
-

Hello {{name}}

-

The time is {{now}}

-
-{{> partials/foot }} diff --git a/implementation/16-data-repository/templates/page.html b/implementation/16-data-repository/templates/page.html deleted file mode 100644 index c3c5284..0000000 --- a/implementation/16-data-repository/templates/page.html +++ /dev/null @@ -1,5 +0,0 @@ -{{> partials/head }} -
- {{{content}}} -
-{{> partials/foot }} diff --git a/implementation/16-data-repository/templates/page/list.html b/implementation/16-data-repository/templates/page/list.html deleted file mode 100644 index bf42348..0000000 --- a/implementation/16-data-repository/templates/page/list.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Pages - - - -
- -
- - \ No newline at end of file diff --git a/implementation/16-data-repository/templates/page/show.html b/implementation/16-data-repository/templates/page/show.html deleted file mode 100644 index abe295e..0000000 --- a/implementation/16-data-repository/templates/page/show.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - {{title}} - - - - - - -
- {{{content}}} -
- - \ No newline at end of file diff --git a/implementation/16-data-repository/templates/pagelist.html b/implementation/16-data-repository/templates/pagelist.html deleted file mode 100644 index 538e2c4..0000000 --- a/implementation/16-data-repository/templates/pagelist.html +++ /dev/null @@ -1,11 +0,0 @@ -{{> partials/head }} -
- -
-{{> partials/foot }} diff --git a/implementation/16-data-repository/templates/partials/foot.html b/implementation/16-data-repository/templates/partials/foot.html deleted file mode 100644 index 17c7245..0000000 --- a/implementation/16-data-repository/templates/partials/foot.html +++ /dev/null @@ -1,3 +0,0 @@ -
- - \ No newline at end of file diff --git a/implementation/16-data-repository/templates/partials/head.html b/implementation/16-data-repository/templates/partials/head.html deleted file mode 100644 index 421d387..0000000 --- a/implementation/16-data-repository/templates/partials/head.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - No Framework: {{title}} - - - - - -
diff --git a/implementation/18-caching/.php-cs-fixer.php b/implementation/18-caching/.php-cs-fixer.php deleted file mode 100644 index 705a7d7..0000000 --- a/implementation/18-caching/.php-cs-fixer.php +++ /dev/null @@ -1,38 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config' - ]) - ); \ No newline at end of file diff --git a/implementation/18-caching/.phpcs.xml.dist b/implementation/18-caching/.phpcs.xml.dist deleted file mode 100644 index 3b433f6..0000000 --- a/implementation/18-caching/.phpcs.xml.dist +++ /dev/null @@ -1,9 +0,0 @@ - - - - - src - config - - - \ No newline at end of file diff --git a/implementation/18-caching/cli-config.php b/implementation/18-caching/cli-config.php deleted file mode 100644 index fbc6598..0000000 --- a/implementation/18-caching/cli-config.php +++ /dev/null @@ -1,13 +0,0 @@ -getContainer(); - -return ConsoleRunner::createHelperSet($container->get(EntityManagerInterface::class)); diff --git a/implementation/18-caching/composer.json b/implementation/18-caching/composer.json deleted file mode 100644 index 29695da..0000000 --- a/implementation/18-caching/composer.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3", - "mustache/mustache": "^2.14", - "psr/http-server-middleware": "^1.0", - "middlewares/trailing-slash": "^2.0", - "middlewares/whoops": "^2.0", - "erusev/parsedown": "^1.7", - "league/commonmark": "^2.2", - "ext-apcu": "*", - "ext-zend-opcache": "*" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require-dev": { - "phpstan/phpstan": "^1.5", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0", - "squizlabs/php_codesniffer": "^3.6", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1", - "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0" - }, - "config": { - "optimize-autoloader": true, - "allow-plugins": { - "phpstan/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "serve": [ - "Composer\\Config::disableProcessTimeout", - "php -S 0.0.0.0:1234 -t public" - ], - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" - } -} diff --git a/implementation/18-caching/composer.lock b/implementation/18-caching/composer.lock deleted file mode 100644 index 40cd7d3..0000000 --- a/implementation/18-caching/composer.lock +++ /dev/null @@ -1,2440 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "9a29468fd456190a9fbcff98ed42d862", - "packages": [ - { - "name": "dflydev/dot-access-data", - "version": "v3.0.1", - "source": { - "type": "git", - "url": "https://github.com/dflydev/dflydev-dot-access-data.git", - "reference": "0992cc19268b259a39e86f296da5f0677841f42c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", - "reference": "0992cc19268b259a39e86f296da5f0677841f42c", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.42", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", - "scrutinizer/ocular": "1.6.0", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^3.14" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Dflydev\\DotAccessData\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dragonfly Development Inc.", - "email": "info@dflydev.com", - "homepage": "http://dflydev.com" - }, - { - "name": "Beau Simensen", - "email": "beau@dflydev.com", - "homepage": "http://beausimensen.com" - }, - { - "name": "Carlos Frutos", - "email": "carlos@kiwing.it", - "homepage": "https://github.com/cfrutos" - }, - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com" - } - ], - "description": "Given a deep data structure, access data by dot notation.", - "homepage": "https://github.com/dflydev/dflydev-dot-access-data", - "keywords": [ - "access", - "data", - "dot", - "notation" - ], - "support": { - "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", - "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" - }, - "time": "2021-08-13T13:06:58+00:00" - }, - { - "name": "erusev/parsedown", - "version": "1.7.4", - "source": { - "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35" - }, - "type": "library", - "autoload": { - "psr-0": { - "Parsedown": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com" - } - ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org", - "keywords": [ - "markdown", - "parser" - ], - "support": { - "issues": "https://github.com/erusev/parsedown/issues", - "source": "https://github.com/erusev/parsedown/tree/1.7.x" - }, - "time": "2019-12-30T22:54:17+00:00" - }, - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.9.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2022-03-29T20:12:16+00:00" - }, - { - "name": "league/commonmark", - "version": "2.2.3", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/commonmark.git", - "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", - "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "league/config": "^1.1.1", - "php": "^7.4 || ^8.0", - "psr/event-dispatcher": "^1.0", - "symfony/deprecation-contracts": "^2.1 || ^3.0", - "symfony/polyfill-php80": "^1.15" - }, - "require-dev": { - "cebe/markdown": "^1.0", - "commonmark/cmark": "0.30.0", - "commonmark/commonmark.js": "0.30.0", - "composer/package-versions-deprecated": "^1.8", - "erusev/parsedown": "^1.0", - "ext-json": "*", - "github/gfm": "0.29.0", - "michelf/php-markdown": "^1.4", - "phpstan/phpstan": "^0.12.88 || ^1.0.0", - "phpunit/phpunit": "^9.5.5", - "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", - "unleashedtech/php-coding-standard": "^3.1", - "vimeo/psalm": "^4.7.3" - }, - "suggest": { - "symfony/yaml": "v2.3+ required if using the Front Matter extension" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.3-dev" - } - }, - "autoload": { - "psr-4": { - "League\\CommonMark\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com", - "role": "Lead Developer" - } - ], - "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", - "homepage": "https://commonmark.thephpleague.com", - "keywords": [ - "commonmark", - "flavored", - "gfm", - "github", - "github-flavored", - "markdown", - "md", - "parser" - ], - "support": { - "docs": "https://commonmark.thephpleague.com/", - "forum": "https://github.com/thephpleague/commonmark/discussions", - "issues": "https://github.com/thephpleague/commonmark/issues", - "rss": "https://github.com/thephpleague/commonmark/releases.atom", - "source": "https://github.com/thephpleague/commonmark" - }, - "funding": [ - { - "url": "https://www.colinodell.com/sponsor", - "type": "custom" - }, - { - "url": "https://www.paypal.me/colinpodell/10.00", - "type": "custom" - }, - { - "url": "https://github.com/colinodell", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/commonmark", - "type": "tidelift" - } - ], - "time": "2022-02-26T21:24:45+00:00" - }, - { - "name": "league/config", - "version": "v1.1.1", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/config.git", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "shasum": "" - }, - "require": { - "dflydev/dot-access-data": "^3.0.1", - "nette/schema": "^1.2", - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.90", - "phpunit/phpunit": "^9.5.5", - "scrutinizer/ocular": "^1.8.1", - "unleashedtech/php-coding-standard": "^3.1", - "vimeo/psalm": "^4.7.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.2-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Config\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com", - "role": "Lead Developer" - } - ], - "description": "Define configuration arrays with strict schemas and access values with dot notation", - "homepage": "https://config.thephpleague.com", - "keywords": [ - "array", - "config", - "configuration", - "dot", - "dot-access", - "nested", - "schema" - ], - "support": { - "docs": "https://config.thephpleague.com/", - "issues": "https://github.com/thephpleague/config/issues", - "rss": "https://github.com/thephpleague/config/releases.atom", - "source": "https://github.com/thephpleague/config" - }, - "funding": [ - { - "url": "https://www.colinodell.com/sponsor", - "type": "custom" - }, - { - "url": "https://www.paypal.me/colinpodell/10.00", - "type": "custom" - }, - { - "url": "https://github.com/colinodell", - "type": "github" - } - ], - "time": "2021-08-14T12:15:32+00:00" - }, - { - "name": "middlewares/trailing-slash", - "version": "v2.0.1", - "source": { - "type": "git", - "url": "https://github.com/middlewares/trailing-slash.git", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", - "shasum": "" - }, - "require": { - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to normalize the trailing slash of the uri path", - "homepage": "https://github.com/middlewares/trailing-slash", - "keywords": [ - "http", - "middleware", - "normalize", - "path", - "psr-15", - "psr-7", - "slash" - ], - "support": { - "issues": "https://github.com/middlewares/trailing-slash/issues", - "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" - }, - "time": "2020-12-02T00:06:55+00:00" - }, - { - "name": "middlewares/utils", - "version": "v3.3.0", - "source": { - "type": "git", - "url": "https://github.com/middlewares/utils.git", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^v2.16", - "guzzlehttp/psr7": "^2.0", - "laminas/laminas-diactoros": "^2.4", - "nyholm/psr7": "^1.0", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "slim/psr7": "^1.4", - "squizlabs/php_codesniffer": "^3.5", - "sunrise/http-message": "^1.0", - "sunrise/http-server-request": "^1.0", - "sunrise/stream": "^1.0.15", - "sunrise/uri": "^1.0.15" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\Utils\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Common utils for PSR-15 middleware packages", - "homepage": "https://github.com/middlewares/utils", - "keywords": [ - "PSR-11", - "http", - "middleware", - "psr-15", - "psr-17", - "psr-7" - ], - "support": { - "issues": "https://github.com/middlewares/utils/issues", - "source": "https://github.com/middlewares/utils/tree/v3.3.0" - }, - "time": "2021-07-04T17:56:23+00:00" - }, - { - "name": "middlewares/whoops", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/middlewares/whoops.git", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", - "shasum": "" - }, - "require": { - "filp/whoops": "^2.5", - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "eloquent/phony-phpunit": "^5.0 || ^7.0", - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to use Whoops as error handler", - "homepage": "https://github.com/middlewares/whoops", - "keywords": [ - "error", - "http", - "middleware", - "psr-15", - "psr-7", - "server", - "whoops" - ], - "support": { - "issues": "https://github.com/middlewares/whoops/issues", - "source": "https://github.com/middlewares/whoops/tree/v2.0.2" - }, - "time": "2022-01-27T20:31:30+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nette/schema", - "version": "v1.2.2", - "source": { - "type": "git", - "url": "https://github.com/nette/schema.git", - "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", - "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", - "shasum": "" - }, - "require": { - "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": ">=7.1 <8.2" - }, - "require-dev": { - "nette/tester": "^2.3 || ^2.4", - "phpstan/phpstan-nette": "^0.12", - "tracy/tracy": "^2.7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "📐 Nette Schema: validating data structures against a given Schema.", - "homepage": "https://nette.org", - "keywords": [ - "config", - "nette" - ], - "support": { - "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.2" - }, - "time": "2021-10-15T11:40:02+00:00" - }, - { - "name": "nette/utils", - "version": "v3.2.7", - "source": { - "type": "git", - "url": "https://github.com/nette/utils.git", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", - "shasum": "" - }, - "require": { - "php": ">=7.2 <8.2" - }, - "conflict": { - "nette/di": "<3.0.6" - }, - "require-dev": { - "nette/tester": "~2.0", - "phpstan/phpstan": "^1.0", - "tracy/tracy": "^2.3" - }, - "suggest": { - "ext-gd": "to use Image", - "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", - "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", - "ext-json": "to use Nette\\Utils\\Json", - "ext-mbstring": "to use Strings::lower() etc...", - "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", - "ext-xml": "to use Strings::length() etc. when mbstring is not available" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", - "homepage": "https://nette.org", - "keywords": [ - "array", - "core", - "datetime", - "images", - "json", - "nette", - "paginator", - "password", - "slugify", - "string", - "unicode", - "utf-8", - "utility", - "validation" - ], - "support": { - "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.2.7" - }, - "time": "2022-01-24T11:29:14+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/http-server-middleware", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-middleware.git", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0", - "psr/http-server-handler": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side middleware", - "keywords": [ - "http", - "http-interop", - "middleware", - "psr", - "psr-15", - "psr-7", - "request", - "response" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-middleware/issues", - "source": "https://github.com/php-fig/http-server-middleware/tree/master" - }, - "time": "2018-10-30T17:12:04+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.0.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "shasum": "" - }, - "require": { - "php": ">=8.0.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:55:41+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-04T08:16:47+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "doctrine/coding-standard", - "version": "8.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/coding-standard.git", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "slevomat/coding-standard": "^6.4.1", - "squizlabs/php_codesniffer": "^3.5.8" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Steve Müller", - "email": "st.mueller@dzh-online.de" - } - ], - "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", - "keywords": [ - "checks", - "code", - "coding", - "cs", - "doctrine", - "rules", - "sniffer", - "sniffs", - "standard", - "style" - ], - "support": { - "issues": "https://github.com/doctrine/coding-standard/issues", - "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" - }, - "time": "2021-04-03T10:54:55+00:00" - }, - { - "name": "mnapoli/hard-mode", - "version": "0.3.0", - "source": { - "type": "git", - "url": "https://github.com/mnapoli/hard-mode.git", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", - "shasum": "" - }, - "require": { - "doctrine/coding-standard": "^8.0" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Hard mode for PHP", - "support": { - "issues": "https://github.com/mnapoli/hard-mode/issues", - "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" - }, - "time": "2020-10-12T07:54:37+00:00" - }, - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.9", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.26", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/master" - }, - "time": "2020-08-03T20:32:43+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.5.4", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", - "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.4" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-04-03T12:39:00+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^1.2.0" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" - }, - "time": "2021-11-18T09:30:29+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "6.4.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpstan/phpstan": "0.12.48", - "phpstan/phpstan-deprecation-rules": "0.12.5", - "phpstan/phpstan-phpunit": "0.12.16", - "phpstan/phpstan-strict-rules": "0.12.5", - "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2020-10-05T12:39:37+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1", - "ext-apcu": "*", - "ext-zend-opcache": "*" - }, - "platform-dev": [], - "plugin-api-version": "2.3.0" -} diff --git a/implementation/18-caching/config/dependencies.php b/implementation/18-caching/config/dependencies.php deleted file mode 100644 index df815c6..0000000 --- a/implementation/18-caching/config/dependencies.php +++ /dev/null @@ -1,58 +0,0 @@ - fn (SystemClockNow $n) => $n, - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, - Emitter::class => fn (BasicEmitter $e) => $e, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, - MarkdownParser::class => fn (ParsedownParser $p) => $p, - MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, - EasyCache::class => fn (ApcuCache $c) => $c, - CachedMarkdownPageRepo::class => fn (EasyCache $c, FileSystemMarkdownPageRepo $r) => new CachedMarkdownPageRepo($c, $r), - - - // Factories - ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), - Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), -]; diff --git a/implementation/18-caching/config/middlewares.php b/implementation/18-caching/config/middlewares.php deleted file mode 100644 index ab662be..0000000 --- a/implementation/18-caching/config/middlewares.php +++ /dev/null @@ -1,13 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/page', [Page::class, 'list']); - $r->addRoute('GET', '/page/{page}', [Page::class, 'show']); - $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); - $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -}; diff --git a/implementation/18-caching/config/settings.php b/implementation/18-caching/config/settings.php deleted file mode 100644 index c654565..0000000 --- a/implementation/18-caching/config/settings.php +++ /dev/null @@ -1,12 +0,0 @@ ->](02-composer.md) - -### Front Controller - -A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. - -To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. - -A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. - -The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. - -But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. - -So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. - -Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: - -```php ->](02-composer.md) diff --git a/implementation/18-caching/data/pages/02-composer.md b/implementation/18-caching/data/pages/02-composer.md deleted file mode 100644 index a25a4a8..0000000 --- a/implementation/18-caching/data/pages/02-composer.md +++ /dev/null @@ -1,75 +0,0 @@ -[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) - -### Composer - -[Composer](https://getcomposer.org/) is a dependency manager for PHP. - -Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do -something. With Composer, you can install third-party libraries for your application. - -If you don't have Composer installed already, head over to the website and install it. You can find Composer packages -for your project on [Packagist](https://packagist.org/). - -Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will -be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. - -Add the following content to the file: - -```json -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubiana", - "email": "lubiana@hannover.ccc.de" - } - ] -} -``` - -In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use -whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. -Just replace it with your namespace in your own code. - -I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. - -As the Bootstrap.php file is placed in that directory we should -add the namespace to the File as well. Here is my current Bootstrap.php -as a reference: - -```php ->](03-error-handler.md) diff --git a/implementation/18-caching/data/pages/03-error-handler.md b/implementation/18-caching/data/pages/03-error-handler.md deleted file mode 100644 index 60465d0..0000000 --- a/implementation/18-caching/data/pages/03-error-handler.md +++ /dev/null @@ -1,79 +0,0 @@ -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) - -### Error Handler - -An error handler allows you to customize what happens if your code results in an error. - -A nice error page with a lot of information for debugging goes a long way during development. So the first package -for your application will take care of that. - -I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. -If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, -you have total control over your project. - -An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) - -To install a new package, open up your `composer.json` and add the package to the require part. It should now look -like this: - -```php -"require": { - "php": ">=8.1.0", - "filp/whoops": "^2.14" -}, -``` - -Now run `composer update` in your console and it will be installed. - -Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, -i that case composer automatically installs the package and updates your composer.json-file. - -But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, -ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you -only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. - -**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message -can help someone to gain access to your system. Always show a user friendly error page instead and send an email to -yourself, write to a log or something similar. So only you can see the errors in the production environment. - -For development that does not make sense though -- you want a nice error page. The solution is to have an environment -switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in -case no environment has been set. - -Then after the error handler registration, throw an `Exception` to test if everything is working correctly. -Your `Bootstrap.php` should now look similar to this: - -```php -pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (\Throwable $e) { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -throw new \Exception("Ooooopsie"); - -``` - -You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until -you get it working. Now would also be a good time for another commit. - - -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/implementation/18-caching/data/pages/04-development-helpers.md b/implementation/18-caching/data/pages/04-development-helpers.md deleted file mode 100644 index 74f913c..0000000 --- a/implementation/18-caching/data/pages/04-development-helpers.md +++ /dev/null @@ -1,260 +0,0 @@ -[<< previous](03-error-handler.md) | [next >>](05-http.md) - -### Development Helpers - -I have added some more helpers to my composer.json that help me with development. As these are scripts and programms -used only for development they should not be used in a production environment. Composer has a specific sections in its -file called "dev-dependencies", everything that is required in this section does not get installen in production. - -Let's install our dev-helpers and i will explain them one by one: -`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` - -#### Static Code Analysis with phpstan - -Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or -create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements -that are not (or not always) available, if-statements that always are true and a lot of other stuff. - -A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. - -```php -/** - * @param \DateTime $date - * @return void - */ -function printDate($date) { - $date->format('Y-m-d H:i:s'); -} - -printDate('now'); -``` -if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` - -It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has -no use, and secondly, that we are calling the function with a string instead of a datetime object. - -```shell -1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - ------ --------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ --------------------------------------------------------------------------------------------- -30 Call to method DateTime::format() on a separate line has no effect. -33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. - ------ --------------------------------------------------------------------------------------------- -``` - -The second error is something that "declare strict-types" already catches for us, but the first error is something that -we usually would not discover easily without speccially looking for this errortype. - -We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and -path everytime we want to check our code for errors: - -```yaml -parameters: - level: max - paths: - - src -``` -now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project - -With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us -some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. - -```shell -composer require --dev phpstan/extension-installer -composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules -``` - -During the first install you need to allow the extension installer to actually install the extension. The second command -installs some more strict rulesets and activates them in phpstan. - -If we now rerun phpstan it already tells us about some errors we have made: - -``` - ------ ----------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ ----------------------------------------------------------------------------------------------- -10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider - using long ternary. -25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: - http://bit.ly/subtypeexception -26 Unreachable statement - code above always terminates. - ------ ----------------------------------------------------------------------------------------------- -``` - -The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove -that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific -problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working -on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages -everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we -are adding to our code. - -In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the -phpstan.neon file and run phpstan with the -'--generate-baseline' option: - -```yaml -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` -```shell -[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline -Note: Using configuration file /home/vagrant/app/phpstan.neon. - 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - - - [OK] Baseline generated with 1 error. - - -``` - -you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) - -#### PHP-CS-Fixer - -Another great tool is the php-cs-fixer, which just applies a specific style to your code. - -when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current -directory. - -You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) - -personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup -a configuration file that i use in all my projects .php-cs-fixer.php: - -```php -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - ]) - ); -``` - -#### PHP Codesniffer - -The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra -rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. - -it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. - -Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have -guessed we are adding some extra rules. - -Lets install the ruleset with composer -```shell -composer require --dev mnapoli/hard-mode -``` - -and add a configuration file to actually use it '.phpcs.xml.dist' -```xml - - - - - src - - - -``` - -running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. - -``` -[vagrant@archlinux app]$ ./vendor/bin/phpcs - -FILE: src/Bootstrap.php ----------------------------------------------------------------------------------------------------- -FOUND 4 ERRORS AFFECTING 4 LINES ----------------------------------------------------------------------------------------------------- - 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. - 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead ----------------------------------------------------------------------------------------------------- -PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY ----------------------------------------------------------------------------------------------------- - -Time: 639ms; Memory: 10MB -``` - -You can then use `./vendor/bin/phpcbf` to try to fix them - - -#### Symfony Var-Dumper - -another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small -functions. - -dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects -or arrays. - -dd() on the other hand is a function that dumps its parameters and then exits the php-script. - -you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. - -#### Composer scripts - -now we have a few commands that are available on the command line. i personally do not like to type complex commands -with lots of parameters by hand all the time, so i added a few lines to my composer.json: - -```json -"scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" -}, -``` - -that way i can just type "composer" followed by the command name in the root of my project. if i want to start the -php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the -time. - -You could also configure PhpStorm to automatically run these commands in the background and highlight the violations -directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my -flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. - -My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before -commiting and pushing the code. - -[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/implementation/18-caching/data/pages/05-http.md b/implementation/18-caching/data/pages/05-http.md deleted file mode 100644 index 6166214..0000000 --- a/implementation/18-caching/data/pages/05-http.md +++ /dev/null @@ -1,124 +0,0 @@ -[<< previous](04-development-helpers.md) | [next >>](06-router.md) - -### HTTP - -PHP already has a few things built in to make working with HTTP easier. For example there are the -[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. - -These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, -if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, -then you will want a class with a nice object-oriented interface that you can use in your application instead. - -Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The -standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php -projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. - -As this is a widely adopted standard there are already several implementations available for us to use. I will choose -the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. - -Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a -[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. - -Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use -that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding -the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). - - -to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit -enter - -Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): - -```php -$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); -$response = new \Laminas\Diactoros\Response; -$response->getBody()->write('Hello World! '); -$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); -``` - -This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. - -In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the -write()-Method on that object. - - -To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: - -```php -echo $response->getBody(); -``` - -This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only -stores data. - -You can play around with the other methods of the Request object and take a look at its content with the dd() function. - -```php -dd($response) -``` - -Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot -be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which -creates a copy of the request object with the changed property and returns that clone: - -```php -$response = $response->withStatus(200); -$response = $response->withAddedHeader('Content-type', 'application/json'); -``` - -If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been -defined this way. - -But if you have been keeping attention you might argue that the following line should not work if the request object is -immutable. - -```php -$response->getBody()->write('Hello World!'); -``` - -The response-body implements a stream interface which is immutable for some reasons that are described in the -[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be -aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in -the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. -I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content -to a response object. - -```php -$body = $response->getBody(); -$body->write('Hello World!'); -$response = $response->withBody($body); -``` - -Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our -output-logic a little bit more. Replace the line that echos the response-body with the following: - -```php -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); -``` - -This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a -webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. - -Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. - -Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter - - -[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/implementation/18-caching/data/pages/06-router.md b/implementation/18-caching/data/pages/06-router.md deleted file mode 100644 index 6c39ae5..0000000 --- a/implementation/18-caching/data/pages/06-router.md +++ /dev/null @@ -1,101 +0,0 @@ -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) - -### Router - -A router dispatches to different handlers depending on rules that you have set up. - -With your current setup it does not matter what URL is used to access the application, it will always result in the same -response. So let's fix that now. - -I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own -favorite package. - -Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) - -By now you know how to install Composer packages, so I will leave that to you. - -Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. - -```php -$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -switch ($routeInfo[0]) { - case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: - $response = (new \Laminas\Diactoros\Response)->withStatus(405); - $response->getBody()->write('Method not allowed'); - $response = $response->withStatus(405); - break; - case \FastRoute\Dispatcher::FOUND: - $handler = $routeInfo[1]; - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - /** @var \Psr\Http\Message\ResponseInterface $response */ - $response = call_user_func($handler, $request); - break; - case \FastRoute\Dispatcher::NOT_FOUND: - default: - $response = (new \Laminas\Diactoros\Response)->withStatus(404); - $response->getBody()->write('Not Found!'); - break; -} -``` - -In the first part of the code, you are registering the available routes for your application. In the second part, the -dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, -we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. -If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. - -This setup might work for really small applications, but once you start adding a few routes your bootstrap file will -quickly get cluttered. So let's move them out into a separate file. - -Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; - -```php -addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}; -``` - -Now let's rewrite the route dispatcher part to use the `Routes.php` file. - -```php -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); -``` - -This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. - -Of course we now need to add the 'config' folder to the configuration files of our -devhelpers so that they can scan that directory as well. - -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/implementation/18-caching/data/pages/07-dispatching-to-a-class.md b/implementation/18-caching/data/pages/07-dispatching-to-a-class.md deleted file mode 100644 index 0c961a4..0000000 --- a/implementation/18-caching/data/pages/07-dispatching-to-a-class.md +++ /dev/null @@ -1,137 +0,0 @@ -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) - -### Dispatching to a Class - -In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). -MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to -learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) -and the followup posts. - -So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). - -We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other -common names are 'Controllers' or 'Actions'. - -Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. -In there, create a `Hello.php` file. - -```php -getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - } -} -``` - -You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) -that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is -fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement -it in some other parts of our application as well. In order to use that Interface we have to require it with composer: -'composer require psr/http-server-handler'. - -The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. -At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. - -Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: - -```php -return function(\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); - $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); -}; -``` - -Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared -the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing -the response to the one we defined for the second route. - -To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: - -```php -case \FastRoute\Dispatcher::FOUND: - $handler = new $routeInfo[1]; - if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { - throw new \Exception('Invalid Requesthandler'); - } - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - assert($response instanceof \Psr\Http\Message\ResponseInterface) - break; -``` - -So instead of just calling a method you are now instantiating an object and then calling the method on it. - -Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. - -And of course don't forget to commit your changes. - -Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still -generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for -example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still -have our whoopsie error-handler but i like to be more explicit in my control flow. - -In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new -Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and -InternalServerError. All three should extend phps Base Exception class. - -Here is my NotFound.php for example. - -```php - $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - break; - case Dispatcher::METHOD_NOT_ALLOWED: - throw new MethodNotAllowed; - - case Dispatcher::NOT_FOUND: - default: - throw new NotFound; - } -} catch (MethodNotAllowed) { - $response = (new Response)->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = (new Response)->withStatus(404); - $response->getBody()->write('Not Found'); -} catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); -} -``` - -Check if our code still works, try to trigger some errors, run phpstan and the fix command -and don't forget to commit your changes. - -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/implementation/18-caching/data/pages/08-inversion-of-control.md b/implementation/18-caching/data/pages/08-inversion-of-control.md deleted file mode 100644 index 21f4f23..0000000 --- a/implementation/18-caching/data/pages/08-inversion-of-control.md +++ /dev/null @@ -1,54 +0,0 @@ -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) - -### Inversion of Control - -In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we -want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then -we would need to edit every one of our request handlers to call a different constructor of the class. - -The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that -instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done -with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). - -If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is -implemented, it will make sense. - -Change your `Hello` action to the following: - -```php -getAttribute('name', 'Stranger'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: - -```php -$handler = new $className($response); -``` - -Of course we need to also update all the other handlers. - -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/implementation/18-caching/data/pages/09-dependency-injector.md b/implementation/18-caching/data/pages/09-dependency-injector.md deleted file mode 100644 index 7f7c6a2..0000000 --- a/implementation/18-caching/data/pages/09-dependency-injector.md +++ /dev/null @@ -1,213 +0,0 @@ -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) - -### Dependency Injector - -A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when -the class is instantiated. - -Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work -with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look -for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). - -I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) -out of the box. - -After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: - -```php -addDefinitions([ - \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), - \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), -]); - -return $builder->build(); -``` - -In this file we create a containerbuilder, add some definitions to it and return the container. -As the container supports autowiring we only need to define services where we want to use a specific implementation of -an interface. - -In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to -tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second -exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. - -Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), -as we will use that extensively. - -Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally -to create our Handler-Object - -```php -$container = require __DIR__ . '/../config/dependencies.php'; -assert($container instanceof \Psr\Container\ContainerInterface); - -$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); -assert($request instanceof \Psr\Http\Message\ServerRequestInterface); -``` - -The other part that has to be changed is the dispatching of the route. Before you had the following code: - -```php -$className = $routeInfo[1]; -$handler = new $className($response); -assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Change that to the following: - -```php -/** @var RequestHandlerInterface $handler */ -$className = $routeInfo[1]; -$handler = $container->get($className); -assert($handler instanceof RequestHandlerInterface); -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Make sure to use the container fetch the response object in the catch blocks as well: - -```php -} catch (MethodNotAllowed) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(404); - $response->getBody()->write('Not Found'); -} -``` - -Now all your controller constructor dependencies will be automatically resolved with PHP-DI. - -We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons -in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call -`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we -want to work with a different date in a test. - -Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation -that creates us a DateTimeImmutable object with the current date and time. - -src/Service/Time/Now.php: -```php -namespace Lubian\NoFramework\Service\Time; - -interface Now -{ - public function __invoke(): \DateTimeImmutable; -} -``` -src/Service/Time/SystemClockNow.php: -```php -namespace Lubian\NoFramework\Service\Time; - -final class SystemClockNow implements Now -{ - - public function __invoke(): \DateTimeImmutable - { - return new \DateTimeImmutable; - } -} -``` -If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and -update the handle-method to use the new class property: - -```php -getAttribute('name', 'Stranger'); - $nowAsString = ($this->now)()->format('H:i:s'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowAsString); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI -automatically figures out what classes are requested in the constructor and tries to create the objects needed. - -But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred -S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: - -```php - public function __construct( - private ResponseInterface $response, - private Now $now, - ) -``` - -When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation -should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: - -```php -\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), -``` - -we could also use the PHP-DI create method to delegate the object creation to the container implementation: -```php -\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), -``` - -this way the container can try to resolve any dependencies that the class might have internally, but prefer the other -method because we are not depending on this specific dependency injection implementation. - -Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are -requesting the Hello action. - -If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As -we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way -we wont get any warnings for this particular issue, but for any other issues we add to our code. - -Update the phpstan.neon file to include a "baseline" file: - -``` -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` - -if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and -ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just -'baseline' - -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/implementation/18-caching/data/pages/10-invoker.md b/implementation/18-caching/data/pages/10-invoker.md deleted file mode 100644 index 3033fae..0000000 --- a/implementation/18-caching/data/pages/10-invoker.md +++ /dev/null @@ -1,102 +0,0 @@ -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) - -### Invoker - -Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the -one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long -with some services and not the whole Requestobject with all its various properties. - -If we take our Hello action for example we only need a response object, the time service and the 'name' information from -the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named -the class hello and it would be redundant to also call the the method hello. So an updated version of that class could -look like this: - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - string $name = 'Stranger', - ): ResponseInterface - { - $body = $this->response->getBody(); - $nowString = $now->get()->format('H:i:s'); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowString); - return $response - ->withBody($body) - ->withStatus(200); - } -} -``` - -It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short -closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the -rootpath of our application yet. - -```php -$r->addRoute('GET', '/hello[/{name}]', Hello::class); -$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); -$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -``` - -In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the -route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that -class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what -arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router -if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple -callable we would need to manually use reflection as well to resolve all the arguments. - -But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) -which handles all of that for us. - -After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo -switch section in the Bootstrap.php file to use the container->call() method: - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2]; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$response = $container->call($handler, $args); -``` - -Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. - -But by now you should know that I do not like to depend on specific implementations and the call method is not defined in -the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container -or any other implementation. - -Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific -container implementation so we could use it with any container that implements the ContainerInterface. And best of all -the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that -we could implement if we ever want to write our own implementation or we could write an adapter that uses a different -class that solves the same problem. - -But for now we are using the solution provided by PHP-DI. -So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2] ?? []; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$invoker = $container->get(InvokerInterface::class); -assert($invoker instanceof InvokerInterface); -$response = $invoker->call($handler, $args); -assert($response instanceof ResponseInterface); -``` - -Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) -by php, and even some more. - -But let us move on to something more fun and add some templating functionality to our application as we are trying to build -a website in the end. - -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/implementation/18-caching/data/pages/11-templating.md b/implementation/18-caching/data/pages/11-templating.md deleted file mode 100644 index 7bfe1aa..0000000 --- a/implementation/18-caching/data/pages/11-templating.md +++ /dev/null @@ -1,236 +0,0 @@ -[<< previous](10-invoker.md) | [next >>](12-configuration.md) - -### Templating - -A template engine is not necessary with PHP because the language itself can take care of that. But it can make things -like escaping values easier. They also make it easier to draw a clear line between your application logic and the -template files which should only put your variables into the HTML code. - -A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please -also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. -Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. - -For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install -that package before you continue (`composer require mustache/mustache`). - -Another well known alternative would be [Twig](http://twig.sensiolabs.org/). - -Now please go and have a look at the source code of the -[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class -does not implement an interface. - -You could just type hint against the concrete class. But the problem with this approach is that you create tight -coupling. - -In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the -implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want -to add functionality to the engine. You can't do that without going back and changing all your code that is tightly -coupled. - -What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need -another implementation, you just implement that interface in your new class and inject the new class instead. - -Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). -This sounds a lot more complicated than it is, so just follow along. - -First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). -This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. -A class can implement multiple interfaces if necessary. - -So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a -new folder in your `src/` folder with the name `Template` where you can put all the template related things. - -In there create a new interface `Renderer.php` that looks like this: - -```php - $data */ - public function render(string $template, array $data = []) : string; -} -``` - -Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file -`MustacheRenderer.php` with the following content: - -```php -engine->render($template, $data); - } -} -``` - -As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple -and only fulfills the interface. - -Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know -which implementation he has to inject when you hint for the interface. Add this line: - -```php -[ - ... - \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) - ->constructor(new Mustache_Engine), -] -``` - -Now update the Hello.php class to require an implementation of our renderer interface -and use that to render a string using mustache syntax. - - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - Renderer $renderer, - string $name = 'Stranger', - ): ResponseInterface { - $body = $response->getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render( - 'Hello {{name}}, the time is {{now}}!', - $data, - ); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} -``` - -Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. -But what we want is template files, so let's go back and change that. - -To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the -`dependencies.php` file and add the following code: - -```php -[ - ... - Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), - Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), -] -``` - -We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. -Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have -to rename all the template files. - -To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the -dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader -in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object -we can then use it in the factory. - -In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: - -``` -

Hello World

-Hello {{ name }} -``` - -Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` - -Navigate to the hello page in your browser to make sure everything works. - -One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies -file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration -values. - -Lets create a 'Settings' class in our './src' Folder: - -```php -addDefinitions([ - Settings::class => fn () => require __DIR__ '/settings.php', - ResponseInterface::class => create(Response::class), - ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), - Renderer::class => fn (ME $me) => new Mustache($me), - MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), -]); - -return $builder->build(); -``` - - - -And as always, don't forget to commit your changes. - - -[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/implementation/18-caching/data/pages/12-configuration.md b/implementation/18-caching/data/pages/12-configuration.md deleted file mode 100644 index 4b60c19..0000000 --- a/implementation/18-caching/data/pages/12-configuration.md +++ /dev/null @@ -1,200 +0,0 @@ -[<< previous](11-templating.md) | [next >>](13-refactoring.md) - -### Configuration - -In the last chapter we added some more definitions to our dependencies.php in that definitions -we needed to pass quite a few configuration settings and filesystem strings to the constructors -of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. - -As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. - -As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. - -Lets create a 'Settings' class in our './src' Folder: - -```php -filePath; - } -} -``` - -If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files -and craft a settings object from them. - -As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another -factory for our Container because everyone should have a Friend :) - -```php -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn () => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} -``` - -For this to work we need to change our dependencies.php file to just return the array of definitions: -And here we can instantly use the Settings object to create our template engine. - -```php - fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), -]; -``` - -Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: - -```php -... -error_reporting(E_ALL); - -$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); -$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); -... -``` - -Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. - -[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/implementation/18-caching/data/pages/13-refactoring.md b/implementation/18-caching/data/pages/13-refactoring.md deleted file mode 100644 index 6dbbb8d..0000000 --- a/implementation/18-caching/data/pages/13-refactoring.md +++ /dev/null @@ -1,373 +0,0 @@ -[<< previous](12-configuration.md) | [next >>](14-middleware.md) - -### Refactoring - -By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no -reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. -After all the bootstrap file should just set up the classes needed for the handling logic and execute them. - -At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. -As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the -method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non -static and extracted an interface. - -'./src/Http/Emitter.php' -```php -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - echo $response->getBody(); - } -} -``` -After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following -code in the Bootstrap.php to emit the response: - -```php -/** @var Emitter $emitter */ -$emitter = $container->get(Emitter::class); -$emitter->emit($response); -``` - -If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily -write an adapter that implements your emitter interface and wraps that more advanced emitter - -Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and -calling the routerhandler that in the passes the request to a function and gets the response. - -For this to steps to be seperated we are going to create two more classes: -1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object -2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the - requestobject, fetches the correct object from the container and calls it to create a response. - -Lets create the HandlerInterface first: - -```php -getAttribute($this->routeAttributeName, false); - assert($handler !== false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} - -``` - -We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. -The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler -as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in -the next chapter. - -```php -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} -``` - -Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. -```php -[ - '...', - Emitter::class => fn (BasicEmitter $e) => $e, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, -], -``` - -And then we can update our Bootstrap.php to fetch all the services and let them handle the request. - -```php -... -$routeMiddleWare = $container->get(MiddlewareInterface::class); -assert($routeMiddleWare instanceof MiddlewareInterface); -$handler = $container->get(RoutedRequestHandler::class); -assert($handler instanceof RequestHandlerInterface); -$emitter = $container->get(Emitter::class); -assert($emitter instanceof Emitter); - -$request = $container->get(ServerRequestInterface::class); -assert($request instanceof ServerRequestInterface); - -$response = $routeMiddleWare->process($request, $handler); -$emitter->emit($response); -``` -Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of -code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). - -So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. - -I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class -should require to function properly. - -* A RequestFactory - We want our Kernel to be able to build the request itself -* An Emitter - Without an Emitter we will not be able to send the response to the client -* RouteMiddleware - To decore the request with the correct handler for the requested route -* RequestHandler - To delegate the request to the correct funtion that creates the response - -As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface -to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our -interface: - -```php -factory::fromGlobals(); - } - - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} -``` - -For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: - -```php -routeMiddleware->process($request, $this->handler); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} - -``` - -We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines - -```php -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); -``` - -You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking -at the Whoops output and adding the needed definitions to the dependencies.php file. - -And as always, don't forget to commit your changes. - -[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/implementation/18-caching/data/pages/14-middleware.md b/implementation/18-caching/data/pages/14-middleware.md deleted file mode 100644 index 81f82a5..0000000 --- a/implementation/18-caching/data/pages/14-middleware.md +++ /dev/null @@ -1,303 +0,0 @@ -[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) - -### Middleware - -In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain -a bit more about what this interface does and why it is used in many applications. - -The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets -passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all -the middlewars to the client/emitter. - -So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the -response after it gets created by our handlers. - -So lets take a look at the middleware and the requesthandler interfaces - -```php -interface MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; -} - -interface RequestHandlerInterface -{ - public function handle(ServerRequestInterface $request): ResponseInterface; -} -``` - -The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a -requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the -response. - -But the middleware could just ignore the handler and produce a response on its own as the interface just requires us -to produce a response. - -A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users -that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the -database. - -In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates -the request with an 'isAuthenticated' attribute. - -If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that -response is not already cached, than we let the handler create the response and store that in the cache for a few -seconds - -```php -interface CacheInterface -{ - public function get(string $key, callable $resolver, int $ttl): mixed; -} -``` - -The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one -defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses -the callable to produce the value and stores it for the time specified in ttl. - -so lets write our caching middleware: - -```php -final class CachingMiddleware implements MiddlewareInterface -{ - public function __construct(private CacheInterface $cache){} - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { - $key = $request->getUri()->getPath(); - return $this->cache->get($key, fn() => $handler->handle($request), 10); - } - return $handler->handle($request); - } -} -``` - -we can also modify the response after it has been created by our application, for example we could implement a gzip -middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser -know that our application is used to serve dank memes: - -```php -final class DankMemeMiddleware implements MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - $response = $handler->handle($request); - return $response->withAddedHeader('Meme', 'Dank'); - } -} -``` - -but for our application we are going to just add two external middlewares: - -* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. -* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware - -```bash -composer require middlewares/trailing-slash -composer require middlewares/whoops -``` - -The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the -application as well as the middleware stack. - -Our desired request -> response flow looks something like this: - - Client - | ^ - v | - Kernel - | ^ - v | - Whoops Middleware - | ^ - v | - TrailingSlash - | ^ - v | - Routing - | ^ - v | - ContainerResolver - | ^ - v | - Controller/Action - -As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every -middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. - -```php -interface Pipeline -{ - public function dispatch(ServerRequestInterface $request): ResponseInterface; -} -``` - -And our implementation looks something like this: - -```php - $middlewares - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} -``` - -Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code -that should produce our response. - -In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument -and store that itself as the current tip. - -There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) - -Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline -Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline - -```php -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} -``` - -And configure the container to use the Factory to create the Pipeline: - -```php - ..., - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - ... -``` -And of course a new file called middlewares.php in our config folder: -```php -pipeline->dispatch($request); -} -``` - -Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our -pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a -dependency container, if you want you can do that as well. - -**A quick note about docblocks:** You might have noticed, that I rarely add docblocks to my the code in the examples, and -when I do it seems kind of random. My philosophy is that I only add docblocks when there is no way to automatically get -the exact type from the code itself. For me docblocks only serve two purposes: help my IDE to understand what it choices -it has for code completion and to help the static analysis to better understand the code. There is a great blogpost -about the [cost and value of DocBlocks](https://localheinz.com/blog/2018/05/06/cost-and-value-of-docblocks/), although it -is written in 2018 at a time before PHP 7.4 was around everything written there is still valid today. - -[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) diff --git a/implementation/18-caching/data/pages/15-adding-content.md b/implementation/18-caching/data/pages/15-adding-content.md deleted file mode 100644 index 64562fa..0000000 --- a/implementation/18-caching/data/pages/15-adding-content.md +++ /dev/null @@ -1,253 +0,0 @@ -[<< previous](14-middleware.md) | [next >>](16-data-repository.md) - -### Adding Content - -By now we did not really display anything but some examples to in our application and it is now time to make our app -display some content. For example we could our app be able to display the Markdown files used in this tutorial as -nicely rendered HTML Pages that can be viewed in the browser instead of the editor you are using. - -So lets start by copying the markdown files to our app directory. I have created a new folder 'data/pages' and placed all -the markdown files in there. - -Next we need a markdown parser, a pretty simple one is [Parsedown](https://parsedown.org/), if you want more features -you could also use the [Commonmark parser](https://commonmark.thephpleague.com/). I will choose Parsedown here, but you -can use whatever you like. - -After installing Parsedown lets write a Markdownparser interface and an implementation using parsedown. - -We only need one function that receives a string of Markdown and returns the HTML represantion (as a string as well). - - - -```php -parser->parse($markdown); - } -} -``` - -We could now use the ParsedownRender class directly in our actions by typehinting the classname as an argument to the -constructor or a method, but as we always want to rely on an interface instead of an implementation we need to define -the the ParsedownRenderer as the correct implementation for the MarkdownRenderer interface in the dependencies file: - -```php -... - \Lubian\NoFramework\Template\MarkdownParser::class => fn(\Lubian\NoFramework\Template\ParsedownParser $p) => $p, -... -``` - -You can test that in our "Other.php" action and try out if the Parser works and is able to render Markdown to HTML: - -```php -public function someFunctionName(ResponseInterface $response, MarkdownParser $parser): ResponseInterface -{ - $html = $parser->parse('This *works* **too!**'); - $response->getBody()->write($html); - return $response->withStatus(200); -} -``` - -But we want to display complete Pages written in Markdown, it would also be neat to be able to display a list of all -available pages. For that we need a few things: - -Firstly we need two new Templates, one for the list of the Pages, and the second one for displaying a single pages -content. Create a new folder in `templates/page` with to files: - -`templates/page/list.html` -```html - - - - - Pages - - - -
- -
- - -``` - -This template iterates over a provided array of pages, each element consists of the two properties: an id and a title, -those are simply displayed using an unordered list. - -`templates/page/show.html` -```html - - - - - {{title}} - - - - - - -
- {{{content}}} -
- - -``` - -The second templates displays a single rendered markdown page. As data it expects the title and the content as array. -I used an extra bracket for the content ```{{{content}}}``` so that the Mustache-Renderer does not escape the provided -html and thereby destroys the the parsed markdown. - -You might have spotted that I added [Pico.css](https://picocss.com/) which is just a very small css framework to make the -pages a little bit nicer to look at. It mostly provides some typography styles that work great with rendered Markdown, -but you can leave that out or use any other css framework you like. There is also some Javascript that adds syntax -highlighting to the code. - -After you have taken care of the templating side we can now create an new Action class with two methods to display use -our markdown files and the templates to create the pages. As we have two templates I propose to use Two methods in our -Action: -`src/Action/Page.php` -```php -function show(string $name): \Psr\Http\Message\ResponseInterface; -function list(): \Psr\Http\Message\ResponseInterface; -``` - -Lets define two routes. `/page` should display the overview of all pages, and if the add the name of chapter to the -route, `/page/adding-content` for example, the show action should be called with the name as a variable: - -`config/routes.php` -```php -$r->addRoute('GET', '/page', [Page::class, 'list']); -$r->addRoute('GET', '/page/{page}', [Page::class, 'show']); -``` - -Here is my Implementation. If have added a little regex replacement in the show method that replaces the links to the -next and previous chapter so that it works with our routing configuration. - -`src/Action/Page.php` -```php -getPages(), - fn (string $filename) => str_contains($filename, $page) - ) - )[0]; - $markdown = file_get_contents($page); - - // fix the next and previous buttons to work with our routing - $markdown = preg_replace('/\(\d\d-/m', '(', $markdown); - $markdown = str_replace('.md)', ')', $markdown); - - $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); - $data = [ - 'title' => substr($page, 3), - 'content' => $this->parser->parse($markdown), - ]; - $html = $this->renderer->render('page/show', $data); - $this->response->getBody()->write($html); - return $this->response; - } - - public function list(): ResponseInterface - { - $pages = array_map(function (string $page) { - $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); - return [ - 'id' => substr($page, 0, 2), - 'title' => substr($page, 3), - ]; - }, $this->getPages()); - $html = $this->renderer->render('page/list', ['pages' => $pages]); - $this->response->getBody()->write($html); - return $this->response; - } - - /** - * @return string[] - */ - private function getPages(): array - { - $files = glob($this->pagesPath . '*.md'); - if ($files === false) { - throw new InternalServerError('cannot read pages'); - } - return $files; - } -} -``` - -You can now navigate your Browser to [localhost:1234/page][http://localhost:1234/page] and try out if everything works. - -Of course this code is far from looking good. We heavily rely on the pages being files in the filesystem, and the action -should never be aware of the filesystem in the first place, also we have a lot of string replacements and other repetetive -code in the file. And phpstan is gonna scream at us a lot, but if we rewrite the code to satisfy all the checks we would -add even more lines to that simple class, so lets move on to the next chapter where we move all the logic to seperate -classes following our holy SOLID principles :) - - -[<< previous](14-middleware.md) | [next >>](16-data-repository.md) diff --git a/implementation/18-caching/data/pages/16-data-repository.md b/implementation/18-caching/data/pages/16-data-repository.md deleted file mode 100644 index d9a3218..0000000 --- a/implementation/18-caching/data/pages/16-data-repository.md +++ /dev/null @@ -1,265 +0,0 @@ -[<< previous](15-adding-content.md) | [next >>](17-performance.md) - -## Data Repository - -At the end of the last chapter I mentioned being unhappy with our Pages action, because there is to much stuff happening -there. We are firstly receiving some Arguments, then we are using those to query the filesytem for the given page, -loading the specific file from the filesystem, rendering the markdown, passing the markdown to the template renderer, -adding the resulting html to the response and then returning the response. - -In order to make our pageaction independent from the filesystem and move the code that is responsible for reading the -files -to a better place I want to introduce -the [Repository Pattern](https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html). - -I want to start by creating a class that represents the Data that is included in a page so that. For now I can spot -three -distrinct attributes. - -* the ID (or chapternumber) -* the title (or name) -* the content - -Currently all those properties are always available, but we might later be able to create new pages and store them, but -at that point in time we are not yet aware of the new available ID, so we should leave that property nullable. This -allows -us to create an object without an id and let the code that actually saves the object to a persistant store define a -valid -id on saving. - -Lets create an new Namespace called `Model` and put a `MarkdownPage.php` class in there: - -```php -dataPath . '*.md'); - if ($files === false) { - throw new InternalServerError('cannot read pages'); - } - return array_map(function (string $filename) { - $content = file_get_contents($filename); - if ($content === false) { - throw new InternalServerError('cannot read pages'); - } - $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); - return new MarkdownPage( - (int) substr($idAndTitle, 0, 2), - substr($idAndTitle, 3), - $content - ); - }, $files); - } - - public function byName(string $name): MarkdownPage - { - $pages = array_values( - array_filter( - $this->all(), - fn (MarkdownPage $p) => $p->title === $name, - ) - ); - - if (count($pages) !== 1) { - throw new NotFound; - } - - return $pages[0]; - } -} -``` - -With that in place we need to add the required `$pagesPath` to our settings class and add specify that in our -configuration. - -`src/Settings.php` - -```php -final class Settings -{ - public function __construct( - public readonly string $environment, - public readonly string $dependenciesFile, - public readonly string $middlewaresFile, - public readonly string $templateDir, - public readonly string $templateExtension, - public readonly string $pagesPath, - ) { - } -} -``` - -`config/settings.php` - -```php -return new Settings( - environment: 'prod', - dependenciesFile: __DIR__ . '/dependencies.php', - middlewaresFile: __DIR__ . '/middlewares.php', - templateDir: __DIR__ . '/../templates', - templateExtension: '.html', - pagesPath: __DIR__ . '/../data/pages/', -); -``` - -Of course we need to define the correct implementation for the container to choose when we are requesting the Repository -interface: -`conf/dependencies.php` - -```php -MarkdownPageRepo::class => fn (FileSystemMarkdownPageRepo $r) => $r, -FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), -``` - -Now you can request the MarkdownPageRepo Interface in your page action and use the defined functions to get the -MarkdownPage -Objects. My `src/Action/Page.php` looks like this now: - -```php -repo->byName($page); - - // fix the next and previous buttons to work with our routing - $content = preg_replace('/\(\d\d-/m', '(', $page->content); - assert(is_string($content)); - $content = str_replace('.md)', ')', $content); - - $data = [ - 'title' => $page->title, - 'content' => $this->parser->parse($content), - ]; - - $html = $this->renderer->render('page/show', $data); - $this->response->getBody()->write($html); - return $this->response; - } - - public function list(): ResponseInterface - { - $pages = array_map(function (MarkdownPage $page) { - return [ - 'id' => $page->id, - 'title' => $page->content, - ]; - }, $this->repo->all()); - - $html = $this->renderer->render('page/list', ['pages' => $pages]); - $this->response->getBody()->write($html); - return $this->response; - } -} -``` - -Check the page in your browser if everything still works, don't forget to run phpstan and the others fixers before -committing your changes and moving on to the next chapter. - -[<< previous](15-adding-content.md) | [next >>](17-performance.md) diff --git a/implementation/18-caching/data/pages/17-performance.md b/implementation/18-caching/data/pages/17-performance.md deleted file mode 100644 index c83c7d5..0000000 --- a/implementation/18-caching/data/pages/17-performance.md +++ /dev/null @@ -1,43 +0,0 @@ -[<< previous](16-data-repository.md) | [next >>](18-caching.md) - -## Autoloading performance - -Although our application is still very small and you should not really experience any performance issues right now, -there are still some things we can already consider and take a look at. If I check the network tab in my browser it takes -about 90-400ms to show a simple rendered markdownpage, with is sort of ok but in my opinion way to long as we are not -really doing anything and do not connect to any external services. Mostly we are just reading around 16 markdown files, -a template, some config files here and there and parse some markdown. So that should not really take that long. - -The problem is, that we heavily rely on autoloading for all our class files, in the `src` folder. And there are also -quite a lot of other files in composers `vendor` directory. To understand while this is becomming we should make -ourselves familiar with how [autoloading in php](https://www.php.net/manual/en/language.oop5.autoload.php) works. - -The basic idea is, that every class that php encounters has to be loaded from somewhere in the filesystem, we could -just require the files manually but that is tedious, unflexible and can often cause errors. - -The problem we are now facing is that the composer autoloader has some rules to determine from where in the filesystem -a class definition might be placed, then the autoloader tries to locate a file by the namespace and classname and if it -exists includes that file. - -If we only have a handfull of classes that does not take a lot of time, but as we are growing with our application this -easily takes longer than necesery, but fortunately composer has some options to speed up the class loading. - -Take a few minutes to read the documentation about [composer autoloader optimization](https://getcomposer.org/doc/articles/autoloader-optimization.md) - -You can try all 3 levels of optimizations, but we are going to stick with the first one for now, so lets create an -optimized classmap. - -`composer dump-autoload -o` - -After composer has finished you can start the devserver again with `composer serve` and take a look at the network tab -in your browsers devtools. - -In my case the response time falls down to under an average of 30ms with some spikes in between, but all in all it looks really good. -You can also try out the different optimization levels and see if you can spot any differences. - -Although the composer manual states not to use the optimtization in a dev environment I personally have not encountered -any errors with the first level of optimizations, so we can use that level here. If you add the line from the documentation -to your `composer.json` so that the autoloader gets optimized everytime we install new packages. - - -[<< previous](16-data-repository.md) | [next >>](18-caching.md) diff --git a/implementation/18-caching/data/pages/18-caching.md b/implementation/18-caching/data/pages/18-caching.md deleted file mode 100644 index 42e9cb1..0000000 --- a/implementation/18-caching/data/pages/18-caching.md +++ /dev/null @@ -1,252 +0,0 @@ -[<< previous](17-performance.md) | [next >>](19-database.md) - -**DISClAIMER** I do not really have a lot of experience when it comes to caching, so this chapter is mostly some random -thoughts and ideas I wanted to explore when writing this tutorial, you should definitely take everything that is being -said here with caution and try to read up on some other sources. But that holds true for the whole tutorial anyway :) - -## Caching - -In the last chapter we greatly improved the perfomance for the lookup of all our classfiles, but currently we do not -have any real bottlenecks in our application like complex queries. - -But in a real application we are going to execute some really heavy and time intensive database queries that can take -quite a while to be completed. - -We can simulate that by adding a simple delay in our `FileSystemMarkdownPageRepo`. - -```php - return array_map(function (string $filename) { - usleep(rand(100, 400) * 1000); - $content = file_get_contents($filename); - if ($content === false) { - throw new InternalServerError('cannot read pages'); - } - $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); - return new MarkdownPage( - (int) substr($idAndTitle, 0, 2), - substr($idAndTitle, 3), - $content - ); -}); -``` - -Here I added a function that pauses the scripts execution for a random time between 100 and 400ms for every markdownpage -in every call of the `all()` method. - -If you open any page or even the listAction in you browser you will see, that it takes quite a time to render that page. -Although this is a silly example we do not really need to query the database on every request, so lets add a way to cache -the database results between requests. - -The PHP-Community has already adressed the issue of having easy to use access to cache libraries, there is the -[PSR-6 Caching Interface](https://www.php-fig.org/psr/psr-6) which gives us easy access to many different implementations, -then there is also a much simpler [PSR-16 Simple Cache](https://www.php-fig.org/psr/psr-16) which makes the use even more -easy, and most Caching Libraries implement Both interfaces anyway. You would think that this is more than enough solutions -to satisfy all the Caching needs around, but the Symfony People decided that Caching should be even simpler and easier -to use and defined their own [Interface](https://symfony.com/doc/current/components/cache.html#cache-component-contracts) -which only needs two methods. You should definitely take a look at the linked documentation as it really blew my mind -when I first encountered it. - -The basic idea is that you provide a callback that computes the requested value. The Cache implementation then checks -if it already has the value stored somewhere and if it doesnt it just executes the callback and stores the value for -future calls. - -It is really simple and great to use. In a real world application you should definitely use that or a PSR-16 implementation -but for this tutorial I wanted to roll out my own solution, so here we go. - -As always we are going to define an interface first, I am going to call it EasyCache and place it in the `Service/Cache` -namespace. I will require only one method which is base on the Symfony Cache Contract, and hast a key, a callback, and -the duration that the item should be cached as arguments. - -```php -cache->get( - $key, - fn () => $this->repo->all(), - 300 - ); - } - - public function byName(string $name): MarkdownPage - { - $key = base64_encode(self::class . 'byName' . $name); - return $this->cache->get( - $key, - fn () => $this->repo->byName($name), - 300 - ); - } -} -``` - -This simple wrapper just requires an EasyCache implementation and a MarkdownPageRepo in the constructor and uses them -to cache all queries for 5 minutes. The beauty is that we are not dependent on any implementation here, so we can switch -out the Repository or the Cache at any point down the road if we want to. - -In order to use that we need to update our `config/dependencies.php` to add an alias for the EasyCache interface as well -as defining our CachedMarkdownPageRepo as implementation for the MarkdownPageRepo interface: - -```php -MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, -EasyCache::class => fn (ApcuCache $c) => $c, -``` - -If we try to access our webpage now, we are getting an error, as PHP-DI has detected a circular dependency that cannot -be autowired. - -The Problem is that our CachedMarkdownPageRepo ist defined as the implementation for the MarkdownPageRepo, but it also -requires that exact interface as a dependency. To resolve this issue we need to manually tell the container how to build -the CachedMarkdownPageRepo by adding another line to the `config/dependencies.php` file: - -```php -CachedMarkdownPageRepo::class => fn (EasyCache $c, FileSystemMarkdownPageRepo $r) => new CachedMarkdownPageRepo($c, $r), -``` - -Here we explicitly require the FileSystemMarkdownPageRepo and us that to create the CachedMarkdownPageRepo object. - -When you now navigate to the pages list or to a specific page the first load should take a while (because of our added delay) -but the following request should be answered blazingly fast. - -Before moving on to the next chapter we can take the caching approach even further, in the middleware chapter I talked -about a simple CachingMiddleware that caches all the GET-Request for some seconds, as they should not change that often, -and we can bypass most of our application logic if we just complelety cache away the responses our application generates, -and return them quite early in our Middleware-Pipeline befor the router gets called, or the invoker calls the action, -which itself uses some other services to fetch all the needed data. - -We will introduce a new `Middleware` namespace to place our `Cache.php` middleware: -```php -getMethod() !== 'GET') { - return $handler->handle($request); - } - $keyHash = base64_encode($request->getUri()->getPath()); - $result = $this->cache->get( - $keyHash, - fn () => Serializer::toString($handler->handle($request)), - 300 - ); - return Serializer::fromString($result); - } -} -``` - -The code is quite straight forward, but you might be confused by the Responseserializer I have added here, we need this -because the response body is a stream object, which doesnt always gets serialized correctly, therefore I use a class from -the laminas project to to all the heavy lifting for us. - -We need to add the now middleware to the `config/middlewares.php` file. - -```php ->](19-database.md) diff --git a/implementation/18-caching/phpstan-baseline.neon b/implementation/18-caching/phpstan-baseline.neon deleted file mode 100644 index 61697a1..0000000 --- a/implementation/18-caching/phpstan-baseline.neon +++ /dev/null @@ -1,7 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" - count: 1 - path: src/Http/InvokerRoutedHandler.php - diff --git a/implementation/18-caching/phpstan.neon b/implementation/18-caching/phpstan.neon deleted file mode 100644 index 2eac45a..0000000 --- a/implementation/18-caching/phpstan.neon +++ /dev/null @@ -1,8 +0,0 @@ -includes: - - phpstan-baseline.neon - -parameters: - level: max - paths: - - src - - config \ No newline at end of file diff --git a/implementation/18-caching/public/css/spectre-exp.min.css b/implementation/18-caching/public/css/spectre-exp.min.css deleted file mode 100644 index d313774..0000000 --- a/implementation/18-caching/public/css/spectre-exp.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/implementation/18-caching/public/css/spectre-icons.min.css b/implementation/18-caching/public/css/spectre-icons.min.css deleted file mode 100644 index 0276f7b..0000000 --- a/implementation/18-caching/public/css/spectre-icons.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/implementation/18-caching/public/css/spectre.min.css b/implementation/18-caching/public/css/spectre.min.css deleted file mode 100644 index 0fe23d9..0000000 --- a/implementation/18-caching/public/css/spectre.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/implementation/18-caching/public/favicon.ico b/implementation/18-caching/public/favicon.ico deleted file mode 100644 index 09499b8b3b3201e0f50088e3ac42e167778d1153..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/18-caching/public/index.php b/implementation/18-caching/public/index.php deleted file mode 100644 index 32f5eb3..0000000 --- a/implementation/18-caching/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render('hello', $data); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/18-caching/src/Action/Other.php b/implementation/18-caching/src/Action/Other.php deleted file mode 100644 index da9ceaf..0000000 --- a/implementation/18-caching/src/Action/Other.php +++ /dev/null @@ -1,16 +0,0 @@ -parse('This *works* **too!**'); - $response->getBody()->write($html); - return $response->withStatus(200); - } -} diff --git a/implementation/18-caching/src/Action/Page.php b/implementation/18-caching/src/Action/Page.php deleted file mode 100644 index 96696e4..0000000 --- a/implementation/18-caching/src/Action/Page.php +++ /dev/null @@ -1,60 +0,0 @@ -repo->byName($page); - - // fix the next and previous buttons to work with our routing - $content = preg_replace('/\(\d\d-/m', '(', $page->content); - assert(is_string($content)); - $content = str_replace('.md)', ')', $content); - - $data = [ - 'title' => $page->title, - 'content' => $this->parser->parse($content), - ]; - - $html = $this->renderer->render('page/show', $data); - $this->response->getBody()->write($html); - return $this->response; - } - - public function list(): ResponseInterface - { - $pages = array_map(function (MarkdownPage $page) { - return [ - 'id' => $page->id, - 'title' => $page->title, - ]; - }, $this->repo->all()); - - $html = $this->renderer->render('page/list', ['pages' => $pages]); - $this->response->getBody()->write($html); - return $this->response; - } -} diff --git a/implementation/18-caching/src/Bootstrap.php b/implementation/18-caching/src/Bootstrap.php deleted file mode 100644 index 3abc2e5..0000000 --- a/implementation/18-caching/src/Bootstrap.php +++ /dev/null @@ -1,40 +0,0 @@ -getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); diff --git a/implementation/18-caching/src/Exception/InternalServerError.php b/implementation/18-caching/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/implementation/18-caching/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ -factory::fromGlobals(); - } - - /** - * @param UriInterface|string $uri - * @param array $serverParams - */ - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} diff --git a/implementation/18-caching/src/Factory/FileSystemSettingsProvider.php b/implementation/18-caching/src/Factory/FileSystemSettingsProvider.php deleted file mode 100644 index f071078..0000000 --- a/implementation/18-caching/src/Factory/FileSystemSettingsProvider.php +++ /dev/null @@ -1,22 +0,0 @@ -filePath; - assert($settings instanceof Settings); - return $settings; - } -} diff --git a/implementation/18-caching/src/Factory/PipelineProvider.php b/implementation/18-caching/src/Factory/PipelineProvider.php deleted file mode 100644 index 77738f8..0000000 --- a/implementation/18-caching/src/Factory/PipelineProvider.php +++ /dev/null @@ -1,25 +0,0 @@ -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} diff --git a/implementation/18-caching/src/Factory/RequestFactory.php b/implementation/18-caching/src/Factory/RequestFactory.php deleted file mode 100644 index 2b17abc..0000000 --- a/implementation/18-caching/src/Factory/RequestFactory.php +++ /dev/null @@ -1,11 +0,0 @@ -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = $settings; - $builder->addDefinitions($dependencies); - // $builder->enableCompilation('/tmp'); - return $builder->build(); - } -} diff --git a/implementation/18-caching/src/Factory/SettingsProvider.php b/implementation/18-caching/src/Factory/SettingsProvider.php deleted file mode 100644 index ce1c5f0..0000000 --- a/implementation/18-caching/src/Factory/SettingsProvider.php +++ /dev/null @@ -1,10 +0,0 @@ -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - - echo $response->getBody(); - } -} diff --git a/implementation/18-caching/src/Http/ContainerPipeline.php b/implementation/18-caching/src/Http/ContainerPipeline.php deleted file mode 100644 index 816cedd..0000000 --- a/implementation/18-caching/src/Http/ContainerPipeline.php +++ /dev/null @@ -1,82 +0,0 @@ - $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} diff --git a/implementation/18-caching/src/Http/Emitter.php b/implementation/18-caching/src/Http/Emitter.php deleted file mode 100644 index ce4c035..0000000 --- a/implementation/18-caching/src/Http/Emitter.php +++ /dev/null @@ -1,10 +0,0 @@ -getAttribute($this->routeAttributeName, false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} diff --git a/implementation/18-caching/src/Http/Pipeline.php b/implementation/18-caching/src/Http/Pipeline.php deleted file mode 100644 index 1a9dcda..0000000 --- a/implementation/18-caching/src/Http/Pipeline.php +++ /dev/null @@ -1,11 +0,0 @@ -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} diff --git a/implementation/18-caching/src/Http/RoutedRequestHandler.php b/implementation/18-caching/src/Http/RoutedRequestHandler.php deleted file mode 100644 index a7407c9..0000000 --- a/implementation/18-caching/src/Http/RoutedRequestHandler.php +++ /dev/null @@ -1,10 +0,0 @@ -pipeline->dispatch($request); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} diff --git a/implementation/18-caching/src/Middleware/Cache.php b/implementation/18-caching/src/Middleware/Cache.php deleted file mode 100644 index 8460761..0000000 --- a/implementation/18-caching/src/Middleware/Cache.php +++ /dev/null @@ -1,38 +0,0 @@ -getMethod() !== 'GET') { - return $handler->handle($request); - } - $keyHash = base64_encode($request->getUri()->getPath()); - $result = $this->cache->get( - $keyHash, - fn () => $this->serializer::toString($handler->handle($request)), - 300 - ); - assert(is_string($result)); - return $this->serializer::fromString($result); - } -} diff --git a/implementation/18-caching/src/Model/MarkdownPage.php b/implementation/18-caching/src/Model/MarkdownPage.php deleted file mode 100644 index df244fd..0000000 --- a/implementation/18-caching/src/Model/MarkdownPage.php +++ /dev/null @@ -1,13 +0,0 @@ -cache->get( - $key, - fn () => $this->repo->all(), - 300 - ); - assert(is_array($result)); - foreach ($result as $page) { - assert($page instanceof MarkdownPage); - } - return $result; - } - - public function byName(string $name): MarkdownPage - { - $key = base64_encode(self::class . 'byName' . $name); - $result = $this->cache->get( - $key, - fn () => $this->repo->byName($name), - 300 - ); - assert($result instanceof MarkdownPage); - return $result; - } -} diff --git a/implementation/18-caching/src/Repository/FileSystemMarkdownPageRepo.php b/implementation/18-caching/src/Repository/FileSystemMarkdownPageRepo.php deleted file mode 100644 index cca350e..0000000 --- a/implementation/18-caching/src/Repository/FileSystemMarkdownPageRepo.php +++ /dev/null @@ -1,61 +0,0 @@ -dataPath . '*.md'); - if ($files === false) { - throw new InternalServerError('cannot read pages'); - } - return array_map(function (string $filename) { - $content = file_get_contents($filename); - if ($content === false) { - throw new InternalServerError('cannot read pages'); - } - $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); - return new MarkdownPage( - (int) substr($idAndTitle, 0, 2), - substr($idAndTitle, 3), - $content - ); - }, $files); - } - - public function byName(string $name): MarkdownPage - { - $pages = array_values( - array_filter( - $this->all(), - fn (MarkdownPage $p) => $p->title === $name, - ) - ); - - if (count($pages) !== 1) { - throw new NotFound; - } - - return $pages[0]; - } -} diff --git a/implementation/18-caching/src/Repository/MarkdownPageRepo.php b/implementation/18-caching/src/Repository/MarkdownPageRepo.php deleted file mode 100644 index 0792d32..0000000 --- a/implementation/18-caching/src/Repository/MarkdownPageRepo.php +++ /dev/null @@ -1,15 +0,0 @@ -engine->render($template, $data); - } -} diff --git a/implementation/18-caching/src/Template/ParsedownParser.php b/implementation/18-caching/src/Template/ParsedownParser.php deleted file mode 100644 index 2ffd287..0000000 --- a/implementation/18-caching/src/Template/ParsedownParser.php +++ /dev/null @@ -1,17 +0,0 @@ -parser->parse($markdown); - } -} diff --git a/implementation/18-caching/src/Template/Renderer.php b/implementation/18-caching/src/Template/Renderer.php deleted file mode 100644 index ff916ed..0000000 --- a/implementation/18-caching/src/Template/Renderer.php +++ /dev/null @@ -1,11 +0,0 @@ - $data - */ - public function render(string $template, array $data = []): string; -} diff --git a/implementation/18-caching/templates/hello.html b/implementation/18-caching/templates/hello.html deleted file mode 100644 index 15a4cd2..0000000 --- a/implementation/18-caching/templates/hello.html +++ /dev/null @@ -1,6 +0,0 @@ -{{> partials/head }} -
-

Hello {{name}}

-

The time is {{now}}

-
-{{> partials/foot }} diff --git a/implementation/18-caching/templates/page.html b/implementation/18-caching/templates/page.html deleted file mode 100644 index c3c5284..0000000 --- a/implementation/18-caching/templates/page.html +++ /dev/null @@ -1,5 +0,0 @@ -{{> partials/head }} -
- {{{content}}} -
-{{> partials/foot }} diff --git a/implementation/18-caching/templates/page/list.html b/implementation/18-caching/templates/page/list.html deleted file mode 100644 index bf42348..0000000 --- a/implementation/18-caching/templates/page/list.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Pages - - - -
- -
- - \ No newline at end of file diff --git a/implementation/18-caching/templates/page/show.html b/implementation/18-caching/templates/page/show.html deleted file mode 100644 index abe295e..0000000 --- a/implementation/18-caching/templates/page/show.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - {{title}} - - - - - - -
- {{{content}}} -
- - \ No newline at end of file diff --git a/implementation/18-caching/templates/pagelist.html b/implementation/18-caching/templates/pagelist.html deleted file mode 100644 index 538e2c4..0000000 --- a/implementation/18-caching/templates/pagelist.html +++ /dev/null @@ -1,11 +0,0 @@ -{{> partials/head }} -
- -
-{{> partials/foot }} diff --git a/implementation/18-caching/templates/partials/foot.html b/implementation/18-caching/templates/partials/foot.html deleted file mode 100644 index 17c7245..0000000 --- a/implementation/18-caching/templates/partials/foot.html +++ /dev/null @@ -1,3 +0,0 @@ -
- - \ No newline at end of file diff --git a/implementation/18-caching/templates/partials/head.html b/implementation/18-caching/templates/partials/head.html deleted file mode 100644 index 421d387..0000000 --- a/implementation/18-caching/templates/partials/head.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - No Framework: {{title}} - - - - - -
diff --git a/to-be-continued.md b/to-be-continued.md deleted file mode 100644 index a13a0fe..0000000 --- a/to-be-continued.md +++ /dev/null @@ -1,17 +0,0 @@ -### To be continued... - -Congratulations. You made it this far. - -I hope you were following the tutorial step by step and not just skipping over the chapters :) - -If you got something out of the tutorial I would appreciate a star. It's the only way for me to see if people are actually reading this :) - -Because this tutorial was so well-received, it inspired me to write a book. The book is a much more up to date version of this tutorial and covers a lot more. Click the link below to check it out (there is also a sample chapter available). - -### [Professional PHP: Building maintainable and secure applications](http://patricklouys.com/professional-php/) - -![](http://patricklouys.com/img/professional-php-thumb.png) - -Thanks for your time, - -Patrick From d81535c3c0e6cc70459abc776955b59994cb9e6d Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 25 Apr 2022 15:19:41 +0200 Subject: [PATCH 125/314] Changing port to 1235 to not clash with smtp default port --- 01-front-controller.md | 2 +- 04-development-helpers.md | 2 +- 07-dispatching-to-a-class.md | 2 +- 10-invoker.md | 2 +- 15-adding-content.md | 2 +- README.md | 2 +- Vagrantfile | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/01-front-controller.md b/01-front-controller.md index 87a12ad..53d17ef 100644 --- a/01-front-controller.md +++ b/01-front-controller.md @@ -38,7 +38,7 @@ Now navigate inside your `src` folder and create a new `Bootstrap.php` file with echo 'Hello World!'; ``` -Now let's see if everything is set up correctly. Open up a console and navigate into your projects `public` folder. In there type `php -S 0.0.0.0:1234` and press enter. This will start the built-in webserver and you can access your page in a browser with `http://localhost:1234`. You should now see the 'hello world' message. +Now let's see if everything is set up correctly. Open up a console and navigate into your projects `public` folder. In there type `php -S 0.0.0.0:1235` and press enter. This will start the built-in webserver and you can access your page in a browser with `http://localhost:1235`. You should now see the 'hello world' message. If there is an error, go back and try to fix it. If you only see a blank page, check the console window where the server is running for errors. diff --git a/04-development-helpers.md b/04-development-helpers.md index 74f913c..2dd943e 100644 --- a/04-development-helpers.md +++ b/04-development-helpers.md @@ -238,7 +238,7 @@ with lots of parameters by hand all the time, so i added a few lines to my compo ```json "scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", + "serve": "php -S 0.0.0.0:1235 -t public", "phpstan": "./vendor/bin/phpstan analyze", "baseline": "./vendor/bin/phpstan analyze --generate-baseline", "check": "./vendor/bin/phpcs", diff --git a/07-dispatching-to-a-class.md b/07-dispatching-to-a-class.md index 0c961a4..c3555ef 100644 --- a/07-dispatching-to-a-class.md +++ b/07-dispatching-to-a-class.md @@ -72,7 +72,7 @@ case \FastRoute\Dispatcher::FOUND: So instead of just calling a method you are now instantiating an object and then calling the method on it. -Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. +Now if you visit `http://localhost:1235/` everything should work. If not, go back and debug. And of course don't forget to commit your changes. diff --git a/10-invoker.md b/10-invoker.md index 3033fae..0ed6b59 100644 --- a/10-invoker.md +++ b/10-invoker.md @@ -65,7 +65,7 @@ $args['request'] = $request; $response = $container->call($handler, $args); ``` -Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. +Try to open [localhost:1235/](http://localhost:1235/) in your browser and check if you are getting redirected to '/hello'. But by now you should know that I do not like to depend on specific implementations and the call method is not defined in the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container diff --git a/15-adding-content.md b/15-adding-content.md index 64562fa..0e85af0 100644 --- a/15-adding-content.md +++ b/15-adding-content.md @@ -241,7 +241,7 @@ class Page } ``` -You can now navigate your Browser to [localhost:1234/page][http://localhost:1234/page] and try out if everything works. +You can now navigate your Browser to [localhost:1235/page][http://localhost:1235/page] and try out if everything works. Of course this code is far from looking good. We heavily rely on the pages being files in the filesystem, and the action should never be aware of the filesystem in the first place, also we have a lot of string replacements and other repetetive diff --git a/README.md b/README.md index a636596..a072b20 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ vagrant ssh cd app ``` -I have exposed the port 1234 to be used in the VM, if you would like to use another one you are free to modify the +I have exposed the port 1235 to be used in the VM, if you would like to use another one you are free to modify the Vagrantfile. diff --git a/Vagrantfile b/Vagrantfile index 7cdc936..1509687 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -7,7 +7,7 @@ Vagrant.configure("2") do |config| v.memory = 2048 v.cpus = 4 end - config.vm.network "forwarded_port", guest: 1234, host: 1234 + config.vm.network "forwarded_port", guest: 1235, host: 1235 config.vm.network "forwarded_port", guest: 22, host: 2200, id: 'ssh' config.vm.synced_folder "./app", "/home/vagrant/app/" config.ssh.username = 'vagrant' From 68d4abab8fda2a7ffb1dce37ecbb06a51232a8b2 Mon Sep 17 00:00:00 2001 From: lubiana Date: Sat, 30 Apr 2022 20:59:05 +0200 Subject: [PATCH 126/314] fix some typos and link to a blogpost about middleware pattern --- 14-middleware.md | 3 ++- README.md | 2 +- Vagrantfile | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/14-middleware.md b/14-middleware.md index 81f82a5..bddfead 100644 --- a/14-middleware.md +++ b/14-middleware.md @@ -7,7 +7,8 @@ a bit more about what this interface does and why it is used in many application The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all -the middlewars to the client/emitter. +the middlewars to the client/emitter. You can check out [this Blogpost](https://doeken.org/blog/middleware-pattern-in-php) +for a more in depth explanation of the middleware pattern. So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the response after it gets created by our handlers. diff --git a/README.md b/README.md index a072b20..951a018 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ actually solve for you. This tutorial is based on the great [tutorial by Patrick Louys](https://github.com/PatrickLouys/no-framework-tutorial). My version is way more opiniated and uses some newer PHP features. But you should still check out his tutorial which is still very great and helped me personally a lot in taking the next step in my knowledge about PHP development. There is -also an [amazon book](https://patricklouys.com/professional-php/) which expands on the topics covered in this tutorial. +also an [amazing book](https://patricklouys.com/professional-php/) which expands on the topics covered in this tutorial. ## Getting started. diff --git a/Vagrantfile b/Vagrantfile index 1509687..dee2435 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -4,8 +4,8 @@ Vagrant.configure("2") do |config| config.vm.box = "archlinux/archlinux" config.vm.provider "virtualbox" do |v| - v.memory = 2048 - v.cpus = 4 + v.memory = 256 + v.cpus = 2 end config.vm.network "forwarded_port", guest: 1235, host: 1235 config.vm.network "forwarded_port", guest: 22, host: 2200, id: 'ssh' From e4fa8b8e427dff7bbb7c8009e0a26620d681958b Mon Sep 17 00:00:00 2001 From: lubiana Date: Sat, 30 Apr 2022 21:24:25 +0200 Subject: [PATCH 127/314] some more typo and readability fixes --- 03-error-handler.md | 2 +- 04-development-helpers.md | 19 +++++++++---------- 06-router.md | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/03-error-handler.md b/03-error-handler.md index 60465d0..d1d7c06 100644 --- a/03-error-handler.md +++ b/03-error-handler.md @@ -58,7 +58,7 @@ $environment = getenv('ENVIRONMENT') ?: 'dev'; error_reporting(E_ALL); $whoops = new Run; -if ($environment == 'dev') { +if ($environment === 'dev') { $whoops->pushHandler(new PrettyPageHandler); } else { $whoops->pushHandler(function (\Throwable $e) { diff --git a/04-development-helpers.md b/04-development-helpers.md index 2dd943e..4469d69 100644 --- a/04-development-helpers.md +++ b/04-development-helpers.md @@ -47,7 +47,7 @@ Line Bootstrap.php The second error is something that "declare strict-types" already catches for us, but the first error is something that we usually would not discover easily without speccially looking for this errortype. -We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and +We can add a simple configfile called `phpstan.neon` to our project so that we do not have to specify the errorlevel and path everytime we want to check our code for errors: ```yaml @@ -90,16 +90,15 @@ on an old legacy codebase and wanted to add static analysis to it but cant becau everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we are adding to our code. -In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the -phpstan.neon file and run phpstan with the -'--generate-baseline' option: +In order to use that we have to add an empty file `phpstan-baseline.neon` to our project, include that in the +`phpstan.neon` file and run phpstan with the `--generate-baseline` option: ```yaml includes: - phpstan-baseline.neon parameters: - level: 9 + level: max paths: - src ``` @@ -127,7 +126,7 @@ directory. You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup -a configuration file that i use in all my projects .php-cs-fixer.php: +a configuration file that i use in all my projects `.php-cs-fixer.php`: ```php @@ -233,8 +232,8 @@ you could just write dd($whoops) somewhere in your bootstrap.php to check how th #### Composer scripts -now we have a few commands that are available on the command line. i personally do not like to type complex commands -with lots of parameters by hand all the time, so i added a few lines to my composer.json: +now we have a few commands that are available on the command line. I personally do not like to type complex commands +with lots of parameters by hand all the time, so I added a few lines to my `composer.json`: ```json "scripts": { diff --git a/06-router.md b/06-router.md index 6c39ae5..10a0c93 100644 --- a/06-router.md +++ b/06-router.md @@ -86,7 +86,7 @@ return function(\FastRoute\RouteCollector $r) { }; ``` -Now let's rewrite the route dispatcher part to use the `Routes.php` file. +Now let's rewrite the route dispatcher part to use the `routes.php` file. ```php $routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; From 44c5e06996f81c54dda6280808a1a3f93230fbe7 Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 2 May 2022 08:38:14 +0200 Subject: [PATCH 128/314] disable composer timeout in development helpers chapter --- 04-development-helpers.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/04-development-helpers.md b/04-development-helpers.md index 4469d69..4979ca0 100644 --- a/04-development-helpers.md +++ b/04-development-helpers.md @@ -237,7 +237,10 @@ with lots of parameters by hand all the time, so I added a few lines to my `comp ```json "scripts": { - "serve": "php -S 0.0.0.0:1235 -t public", + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], "phpstan": "./vendor/bin/phpstan analyze", "baseline": "./vendor/bin/phpstan analyze --generate-baseline", "check": "./vendor/bin/phpcs", @@ -245,8 +248,8 @@ with lots of parameters by hand all the time, so I added a few lines to my `comp }, ``` -that way i can just type "composer" followed by the command name in the root of my project. if i want to start the -php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the +that way I can just type "composer" followed by the command name in the root of my project. if i want to start the +php devserver I can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the time. You could also configure PhpStorm to automatically run these commands in the background and highlight the violations From 8f7d95d86f223d38ce43fe635fda14509e3ebd66 Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 2 May 2022 08:39:48 +0200 Subject: [PATCH 129/314] readability fixes in chapters 7 and 9 --- 07-dispatching-to-a-class.md | 4 ++-- 09-dependency-injector.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/07-dispatching-to-a-class.md b/07-dispatching-to-a-class.md index c3555ef..830a30f 100644 --- a/07-dispatching-to-a-class.md +++ b/07-dispatching-to-a-class.md @@ -12,7 +12,7 @@ So forget about MVC and instead let's worry about [separation of concerns](http: We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other common names are 'Controllers' or 'Actions'. -Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. +Create a new folder inside the `src/` folder with the name `Action`. In this folder we will place all our action classes. In there, create a `Hello.php` file. ```php @@ -36,7 +36,7 @@ You can see that we implement the [RequestHandlerInterface](https://github.com/p that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement it in some other parts of our application as well. In order to use that Interface we have to require it with composer: -'composer require psr/http-server-handler'. +`composer require psr/http-server-handler`. The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. diff --git a/09-dependency-injector.md b/09-dependency-injector.md index 7f7c6a2..cfbeb7a 100644 --- a/09-dependency-injector.md +++ b/09-dependency-injector.md @@ -12,7 +12,7 @@ for a [suitable solution on packagist](https://packagist.org/providers/psr/conta I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) out of the box. -After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: +After installing the container through composer create a new file with the name `dependencies.php` in your config folder: ```php Date: Mon, 2 May 2022 08:58:41 +0200 Subject: [PATCH 130/314] bump memory in vagrantfile to 512mb --- Vagrantfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Vagrantfile b/Vagrantfile index dee2435..fb02d72 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -4,7 +4,7 @@ Vagrant.configure("2") do |config| config.vm.box = "archlinux/archlinux" config.vm.provider "virtualbox" do |v| - v.memory = 256 + v.memory = 512 v.cpus = 2 end config.vm.network "forwarded_port", guest: 1235, host: 1235 From 99a31e45d0000e8f3f4d20d2945e2fa1251e1fc6 Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 2 May 2022 14:36:41 +0200 Subject: [PATCH 131/314] Update author name in composer chapter --- 02-composer.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/02-composer.md b/02-composer.md index a25a4a8..6bd74e5 100644 --- a/02-composer.md +++ b/02-composer.md @@ -28,8 +28,8 @@ Add the following content to the file: }, "authors": [ { - "name": "lubiana", - "email": "lubiana@hannover.ccc.de" + "name": "example", + "email": "test@example.com" } ] } From e92869c00c3189a8df836706bc8412a71403364b Mon Sep 17 00:00:00 2001 From: lubiana Date: Fri, 13 May 2022 14:52:56 +0200 Subject: [PATCH 132/314] update devhelpers to use ecs instead of phpcs and php-cs-fixer --- 04-development-helpers.md | 141 +++++++++++++++++++++++++------------- 1 file changed, 94 insertions(+), 47 deletions(-) diff --git a/04-development-helpers.md b/04-development-helpers.md index 4979ca0..01f6e62 100644 --- a/04-development-helpers.md +++ b/04-development-helpers.md @@ -7,7 +7,7 @@ used only for development they should not be used in a production environment. C file called "dev-dependencies", everything that is required in this section does not get installen in production. Let's install our dev-helpers and i will explain them one by one: -`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` +`composer require --dev phpstan/phpstan symfony/var-dumper slevomat/coding-standard symplify/easy-coding-standard` #### Static Code Analysis with phpstan @@ -116,57 +116,104 @@ Note: Using configuration file /home/vagrant/app/phpstan.neon. you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) -#### PHP-CS-Fixer +#### Easy-Coding-Standard -Another great tool is the php-cs-fixer, which just applies a specific style to your code. +There are two great tools that help us with applying a consistent coding style to our project as well as check and +automatically fix some other errors and oversights that we might not bother with when writing our code. -when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current -directory. +The first one is [PHP Coding Standards Fixer](https://cs.symfony.com/) which can automatically detect violations of +a defined coding standard and fix them. The second tool is [PHP CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) +which basically does the same has in my experience some more Rules available that we can apply to our code. -You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) - -personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup -a configuration file that i use in all my projects `.php-cs-fixer.php`: +But we are going to use neither of those tools directly and instead choose the [Easy Coding Standard](https://github.com/symplify/easy-coding-standard) +which allows us to combine rules from both mentioned tools, and also claims to run much faster. You could check out the +documentation and decide on your own coding standard. Or use the one provided by me, which is base on PSR-12 but adds +some highly opiniated options. First create a file 'ecs.php' and either add your own configuration or copy the my +prepared one: ```php setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - ]) - ); + +use PhpCsFixer\Fixer\Import\OrderedImportsFixer; +use PhpCsFixer\Fixer\PhpTag\BlankLineAfterOpeningTagFixer; +use SlevomatCodingStandard\Sniffs\Classes\ClassConstantVisibilitySniff; +use SlevomatCodingStandard\Sniffs\Namespaces\AlphabeticallySortedUsesSniff; +use SlevomatCodingStandard\Sniffs\Namespaces\DisallowGroupUseSniff; +use SlevomatCodingStandard\Sniffs\Namespaces\MultipleUsesPerLineSniff; +use SlevomatCodingStandard\Sniffs\Namespaces\NamespaceSpacingSniff; +use SlevomatCodingStandard\Sniffs\Namespaces\ReferenceUsedNamesOnlySniff; +use SlevomatCodingStandard\Sniffs\Namespaces\UseSpacingSniff; +use SlevomatCodingStandard\Sniffs\TypeHints\DeclareStrictTypesSniff; +use SlevomatCodingStandard\Sniffs\TypeHints\UnionTypeHintFormatSniff; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use Symplify\EasyCodingStandard\ValueObject\Option; +use Symplify\EasyCodingStandard\ValueObject\Set\SetList; + +return static function (ContainerConfigurator $containerConfigurator): void { + $parameters = $containerConfigurator->parameters(); + $parameters->set(Option::PATHS, [__DIR__ . '/src', __DIR__ . '/ecs.php']); + $parameters->set(Option::PARALLEL, true); + $parameters->set(Option::SKIP, [BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class]); + + $containerConfigurator->import(SetList::PSR_12); + $containerConfigurator->import(SetList::STRICT); + $containerConfigurator->import(SetList::ARRAY); + $containerConfigurator->import(SetList::SPACES); + $containerConfigurator->import(SetList::DOCBLOCK); + $containerConfigurator->import(SetList::CLEAN_CODE); + $containerConfigurator->import(SetList::COMMON); + $containerConfigurator->import(SetList::COMMENTS); + $containerConfigurator->import(SetList::NAMESPACES); + $containerConfigurator->import(SetList::SYMPLIFY); + $containerConfigurator->import(SetList::CONTROL_STRUCTURES); + + $services = $containerConfigurator->services(); + + // force visibitily declaration on class constants + $services->set(ClassConstantVisibilitySniff::class) + ->property('fixable', true); + + // sort all use statements + $services->set(AlphabeticallySortedUsesSniff::class); + + $services->set(DisallowGroupUseSniff::class); + $services->set(MultipleUsesPerLineSniff::class); + $services->set(NamespaceSpacingSniff::class); + + // import all namespaces, and event php core functions and classes + $services->set(ReferenceUsedNamesOnlySniff::class) + ->property('allowFallbackGlobalConstants', false) + ->property('allowFallbackGlobalFunctions', false) + ->property('allowFullyQualifiedGlobalClasses', false) + ->property('allowFullyQualifiedGlobalConstants', false) + ->property('allowFullyQualifiedGlobalFunctions', false) + ->property('allowFullyQualifiedNameForCollidingClasses', true) + ->property('allowFullyQualifiedNameForCollidingConstants', true) + ->property('allowFullyQualifiedNameForCollidingFunctions', true) + ->property('searchAnnotations', true) + ->property('fixable', true); + + // define newlines between use statements + $services->set(UseSpacingSniff::class) + ->property('linesCountBeforeFirstUse', 1) + ->property('linesCountBetweenUseTypes', 1) + ->property('linesCountAfterLastUse', 1); + + // strict types declaration should be on same line as opening tag + $services->set(DeclareStrictTypesSniff::class) + ->property('declareOnFirstLine', true) + ->property('spacesCountAroundEqualsSign', 0); + + // disallow ?Foo typehint in favor of Foo|null + $services->set(UnionTypeHintFormatSniff::class) + ->property('withSpaces', 'no') + ->property('shortNullable', 'no') + ->property('nullPosition', 'last'); +}; + ``` +You can now use `./vendor/bin/ecs` to list all violations of the defined standard and `./vendor/bin/ecs --fix` to +automatically fix them. #### PHP Codesniffer @@ -223,7 +270,7 @@ You can then use `./vendor/bin/phpcbf` to try to fix them another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small functions. -dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects +dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects or arrays. dd() on the other hand is a function that dumps its parameters and then exits the php-script. @@ -259,4 +306,4 @@ flow when programming and always forces me to be absolutely strict even if I am My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before commiting and pushing the code. -[<< previous](03-error-handler.md) | [next >>](05-http.md) +[<< previous](03-error-handler.md) | [next >>](05-http.md) \ No newline at end of file From fde9b5c11ee259622a42d4a6bafa93adeb96bfb8 Mon Sep 17 00:00:00 2001 From: lubiana Date: Wed, 18 May 2022 07:22:26 +0200 Subject: [PATCH 133/314] update development helpers chapter --- 04-development-helpers.md | 59 ++++----------------------------------- 1 file changed, 5 insertions(+), 54 deletions(-) diff --git a/04-development-helpers.md b/04-development-helpers.md index 01f6e62..0da4759 100644 --- a/04-development-helpers.md +++ b/04-development-helpers.md @@ -215,56 +215,6 @@ return static function (ContainerConfigurator $containerConfigurator): void { You can now use `./vendor/bin/ecs` to list all violations of the defined standard and `./vendor/bin/ecs --fix` to automatically fix them. -#### PHP Codesniffer - -The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra -rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. - -it provides the `phpcs` command to check for violations and the `phpcbf` command to actually fix most of the violations. - -Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have -guessed we are adding some extra rules. - -Lets install the ruleset with composer -```shell -composer require --dev mnapoli/hard-mode -``` - -and add a configuration file to actually use it `.phpcs.xml.dist` -```xml - - - - - src - - - -``` - -running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. - -``` -[vagrant@archlinux app]$ ./vendor/bin/phpcs - -FILE: src/Bootstrap.php ----------------------------------------------------------------------------------------------------- -FOUND 4 ERRORS AFFECTING 4 LINES ----------------------------------------------------------------------------------------------------- - 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. - 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead ----------------------------------------------------------------------------------------------------- -PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY ----------------------------------------------------------------------------------------------------- - -Time: 639ms; Memory: 10MB -``` - -You can then use `./vendor/bin/phpcbf` to try to fix them - - #### Symfony Var-Dumper another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small @@ -290,8 +240,8 @@ with lots of parameters by hand all the time, so I added a few lines to my `comp ], "phpstan": "./vendor/bin/phpstan analyze", "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix" }, ``` @@ -303,7 +253,8 @@ You could also configure PhpStorm to automatically run these commands in the bac directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. -My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before -commiting and pushing the code. +My workflow is to just write my code the way I currently feel and that execute the phpstan and the fix scripts before +commiting and pushing the code. There is a [highly opiniated blogpost](https://tomasvotruba.com/blog/2019/06/24/do-you-use-php-codesniffer-and-php-cs-fixer-phpstorm-plugin-you-are-slow-and-expensive/) +discussing that topic further. That you can read. But in the end it boils down to what you are most comfortable with. [<< previous](03-error-handler.md) | [next >>](05-http.md) \ No newline at end of file From 63a5ae837c29f457dbf30f43a93378c2c28b1807 Mon Sep 17 00:00:00 2001 From: lubiana Date: Wed, 18 May 2022 07:40:45 +0200 Subject: [PATCH 134/314] add rector to dev helpers --- 04-development-helpers.md | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/04-development-helpers.md b/04-development-helpers.md index 0da4759..bde29e0 100644 --- a/04-development-helpers.md +++ b/04-development-helpers.md @@ -7,7 +7,7 @@ used only for development they should not be used in a production environment. C file called "dev-dependencies", everything that is required in this section does not get installen in production. Let's install our dev-helpers and i will explain them one by one: -`composer require --dev phpstan/phpstan symfony/var-dumper slevomat/coding-standard symplify/easy-coding-standard` +`composer require --dev phpstan/phpstan symfony/var-dumper slevomat/coding-standard symplify/easy-coding-standard rector/rector` #### Static Code Analysis with phpstan @@ -215,6 +215,37 @@ return static function (ContainerConfigurator $containerConfigurator): void { You can now use `./vendor/bin/ecs` to list all violations of the defined standard and `./vendor/bin/ecs --fix` to automatically fix them. +#### Rector + +The next tool helps us with automatic refactorings and upgrades to newer PHP versions. + +Place a file called `rector.php` in your app directory and put in the following content: + +```php +paths([__DIR__ . '/src', __DIR__ . '/config']); + + $rectorConfig->importNames(); + + $rectorConfig->sets([ + LevelSetList::UP_TO_PHP_81, + ]); +}; +``` + +This config fixes your code and replaces function call and constructs that are deprecated in modern php versions. This +includes all fixes from PHP 5.2 up to PHP 8.1. You can take a look at all the rules [here](https://github.com/rectorphp/rector/blob/main/docs/rector_rules_overview.md#php52). + +To run this tool simply type `./vendor/bin/rector process` in your console. This should not to much right now, but will +be quite useful when php 8.2 or newer versions are released. + #### Symfony Var-Dumper another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small @@ -241,7 +272,8 @@ with lots of parameters by hand all the time, so I added a few lines to my `comp "phpstan": "./vendor/bin/phpstan analyze", "baseline": "./vendor/bin/phpstan analyze --generate-baseline", "check": "./vendor/bin/ecs", - "fix": "./vendor/bin/ecs --fix" + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" }, ``` From 3f832d7c9421e46ac078cbad424b89dc84ee3d96 Mon Sep 17 00:00:00 2001 From: lubiana Date: Wed, 18 May 2022 08:32:23 +0200 Subject: [PATCH 135/314] update ecs config to newer version in devhelper chapter --- 04-development-helpers.md | 46 ++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/04-development-helpers.md b/04-development-helpers.md index bde29e0..b2aece8 100644 --- a/04-development-helpers.md +++ b/04-development-helpers.md @@ -135,8 +135,10 @@ prepared one: parameters(); +return static function (ECSConfig $config): void { + $parameters = $config->parameters(); $parameters->set(Option::PATHS, [__DIR__ . '/src', __DIR__ . '/ecs.php']); $parameters->set(Option::PARALLEL, true); - $parameters->set(Option::SKIP, [BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class]); + $parameters->set( + Option::SKIP, + [BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class] + ); - $containerConfigurator->import(SetList::PSR_12); - $containerConfigurator->import(SetList::STRICT); - $containerConfigurator->import(SetList::ARRAY); - $containerConfigurator->import(SetList::SPACES); - $containerConfigurator->import(SetList::DOCBLOCK); - $containerConfigurator->import(SetList::CLEAN_CODE); - $containerConfigurator->import(SetList::COMMON); - $containerConfigurator->import(SetList::COMMENTS); - $containerConfigurator->import(SetList::NAMESPACES); - $containerConfigurator->import(SetList::SYMPLIFY); - $containerConfigurator->import(SetList::CONTROL_STRUCTURES); + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); - $services = $containerConfigurator->services(); + $services = $config->services(); - // force visibitily declaration on class constants + // force visibility declaration on class constants $services->set(ClassConstantVisibilitySniff::class) ->property('fixable', true); // sort all use statements $services->set(AlphabeticallySortedUsesSniff::class); - $services->set(DisallowGroupUseSniff::class); $services->set(MultipleUsesPerLineSniff::class); $services->set(NamespaceSpacingSniff::class); @@ -209,8 +215,12 @@ return static function (ContainerConfigurator $containerConfigurator): void { ->property('withSpaces', 'no') ->property('shortNullable', 'no') ->property('nullPosition', 'last'); + + // Remove useless parantheses in new statements + $services->set(NewWithoutParenthesesSniff::class); }; + ``` You can now use `./vendor/bin/ecs` to list all violations of the defined standard and `./vendor/bin/ecs --fix` to automatically fix them. From 997d160796d9a2123f01c43d9b5ca00b76bb614b Mon Sep 17 00:00:00 2001 From: lubiana Date: Thu, 19 May 2022 23:34:22 +0200 Subject: [PATCH 136/314] enable intl extension --- Vagrantfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Vagrantfile b/Vagrantfile index fb02d72..c1df0b9 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -23,5 +23,6 @@ Vagrant.configure("2") do |config| echo -e 'zend.assertions=1\n' >> /etc/php/conf.d/tutorial.ini echo -e 'opcache.enable=1\nopcache.enable_cli=1\n' >> /etc/php/conf.d/tutorial.ini echo -e 'acp.enable=1\napc.enable_cli=1\n' >> /etc/php/conf.d/tutorial.ini + echo -e 'extension=intl\n' >> /etc/php/conf.d/tutorial.ini SHELL end From 6818179857a76c658e3bdd6159deb39740ff786e Mon Sep 17 00:00:00 2001 From: lubiana Date: Thu, 19 May 2022 23:35:10 +0200 Subject: [PATCH 137/314] update ecs and rector config --- 04-development-helpers.md | 87 ++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/04-development-helpers.md b/04-development-helpers.md index b2aece8..b9fcd50 100644 --- a/04-development-helpers.md +++ b/04-development-helpers.md @@ -148,17 +148,12 @@ use SlevomatCodingStandard\Sniffs\Namespaces\UseSpacingSniff; use SlevomatCodingStandard\Sniffs\TypeHints\DeclareStrictTypesSniff; use SlevomatCodingStandard\Sniffs\TypeHints\UnionTypeHintFormatSniff; use Symplify\EasyCodingStandard\Config\ECSConfig; -use Symplify\EasyCodingStandard\ValueObject\Option; use Symplify\EasyCodingStandard\ValueObject\Set\SetList; return static function (ECSConfig $config): void { - $parameters = $config->parameters(); - $parameters->set(Option::PATHS, [__DIR__ . '/src', __DIR__ . '/ecs.php']); - $parameters->set(Option::PARALLEL, true); - $parameters->set( - Option::SKIP, - [BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class] - ); + $config->parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); $config->sets([ SetList::PSR_12, @@ -174,53 +169,59 @@ return static function (ECSConfig $config): void { SetList::CONTROL_STRUCTURES, ]); - $services = $config->services(); - // force visibility declaration on class constants - $services->set(ClassConstantVisibilitySniff::class) - ->property('fixable', true); + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); // sort all use statements - $services->set(AlphabeticallySortedUsesSniff::class); - $services->set(DisallowGroupUseSniff::class); - $services->set(MultipleUsesPerLineSniff::class); - $services->set(NamespaceSpacingSniff::class); + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); - // import all namespaces, and event php core functions and classes - $services->set(ReferenceUsedNamesOnlySniff::class) - ->property('allowFallbackGlobalConstants', false) - ->property('allowFallbackGlobalFunctions', false) - ->property('allowFullyQualifiedGlobalClasses', false) - ->property('allowFullyQualifiedGlobalConstants', false) - ->property('allowFullyQualifiedGlobalFunctions', false) - ->property('allowFullyQualifiedNameForCollidingClasses', true) - ->property('allowFullyQualifiedNameForCollidingConstants', true) - ->property('allowFullyQualifiedNameForCollidingFunctions', true) - ->property('searchAnnotations', true) - ->property('fixable', true); + // import all namespaces, and even php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); // define newlines between use statements - $services->set(UseSpacingSniff::class) - ->property('linesCountBeforeFirstUse', 1) - ->property('linesCountBetweenUseTypes', 1) - ->property('linesCountAfterLastUse', 1); + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); // strict types declaration should be on same line as opening tag - $services->set(DeclareStrictTypesSniff::class) - ->property('declareOnFirstLine', true) - ->property('spacesCountAroundEqualsSign', 0); + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); // disallow ?Foo typehint in favor of Foo|null - $services->set(UnionTypeHintFormatSniff::class) - ->property('withSpaces', 'no') - ->property('shortNullable', 'no') - ->property('nullPosition', 'last'); + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); - // Remove useless parantheses in new statements - $services->set(NewWithoutParenthesesSniff::class); + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); }; - ``` You can now use `./vendor/bin/ecs` to list all violations of the defined standard and `./vendor/bin/ecs --fix` to automatically fix them. @@ -240,7 +241,7 @@ use Rector\Config\RectorConfig; use Rector\Set\ValueObject\LevelSetList; return static function (RectorConfig $rectorConfig): void { - $rectorConfig->paths([__DIR__ . '/src', __DIR__ . '/config']); + $rectorConfig->paths([__DIR__ . '/src', __DIR__ . '/rector.php', __DIR__ . '/ecs.php']); $rectorConfig->importNames(); From 7ff078b16fa8d144ed46813d150b432976f4cd21 Mon Sep 17 00:00:00 2001 From: lubiana Date: Thu, 19 May 2022 23:35:51 +0200 Subject: [PATCH 138/314] fix wrong namespace in for laminas request in http chapter --- 05-http.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/05-http.md b/05-http.md index 6166214..61077a6 100644 --- a/05-http.md +++ b/05-http.md @@ -30,7 +30,7 @@ enter Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): ```php -$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); +$request = \Laminas\Diactoros\ServerRequestFactory::fromGlobals(); $response = new \Laminas\Diactoros\Response; $response->getBody()->write('Hello World! '); $response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); From a4f171b98c3baef7928670e9929af0e2e10f43be Mon Sep 17 00:00:00 2001 From: lubiana Date: Thu, 19 May 2022 23:39:08 +0200 Subject: [PATCH 139/314] readd implementation folder --- implementation/03/composer.json | 18 + implementation/03/public/favicon.ico | Bin 0 -> 15086 bytes implementation/03/public/index.php | 3 + implementation/03/src/Bootstrap.php | 27 + implementation/04/composer.json | 45 + implementation/04/composer.lock | 872 ++++++++++++ implementation/04/ecs.php | 89 ++ implementation/04/phpstan-baseline.neon | 6 + implementation/04/phpstan.neon | 7 + implementation/04/public/favicon.ico | Bin 0 -> 15086 bytes implementation/04/public/index.php | 3 + implementation/04/rector.php | 11 + implementation/04/src/Bootstrap.php | 34 + implementation/05/composer.json | 46 + implementation/05/composer.lock | 1079 +++++++++++++++ implementation/05/ecs.php | 89 ++ implementation/05/phpstan-baseline.neon | 6 + implementation/05/phpstan.neon | 7 + implementation/05/public/favicon.ico | Bin 0 -> 15086 bytes implementation/05/public/index.php | 3 + implementation/05/rector.php | 11 + implementation/05/src/Bootstrap.php | 63 + implementation/06/composer.json | 47 + implementation/06/composer.lock | 1129 ++++++++++++++++ implementation/06/config/routes.php | 21 + implementation/06/ecs.php | 89 ++ implementation/06/phpstan-baseline.neon | 6 + implementation/06/phpstan.neon | 8 + implementation/06/public/favicon.ico | Bin 0 -> 15086 bytes implementation/06/public/index.php | 3 + implementation/06/rector.php | 12 + implementation/06/src/Bootstrap.php | 97 ++ implementation/07/composer.json | 48 + implementation/07/composer.lock | 1186 +++++++++++++++++ implementation/07/config/routes.php | 10 + implementation/07/ecs.php | 89 ++ implementation/07/phpstan-baseline.neon | 6 + implementation/07/phpstan.neon | 8 + implementation/07/public/favicon.ico | Bin 0 -> 15086 bytes implementation/07/public/index.php | 3 + implementation/07/rector.php | 12 + implementation/07/src/Action/Hello.php | 20 + implementation/07/src/Action/Other.php | 19 + implementation/07/src/Bootstrap.php | 101 ++ .../07/src/Exception/InternalServerError.php | 9 + .../07/src/Exception/MethodNotAllowed.php | 9 + implementation/07/src/Exception/NotFound.php | 9 + implementation/08/composer.json | 48 + implementation/08/composer.lock | 1186 +++++++++++++++++ implementation/08/config/routes.php | 10 + implementation/08/ecs.php | 89 ++ implementation/08/phpstan-baseline.neon | 6 + implementation/08/phpstan.neon | 8 + implementation/08/public/favicon.ico | Bin 0 -> 15086 bytes implementation/08/public/index.php | 3 + implementation/08/rector.php | 12 + implementation/08/src/Action/Hello.php | 25 + implementation/08/src/Action/Other.php | 24 + implementation/08/src/Bootstrap.php | 101 ++ .../08/src/Exception/InternalServerError.php | 9 + .../08/src/Exception/MethodNotAllowed.php | 9 + implementation/08/src/Exception/NotFound.php | 9 + implementation/09-wip/composer.json | 48 + implementation/09-wip/composer.lock | 1186 +++++++++++++++++ implementation/09-wip/config/container.php | 9 + implementation/09-wip/config/routes.php | 10 + implementation/09-wip/ecs.php | 89 ++ implementation/09-wip/phpstan-baseline.neon | 6 + implementation/09-wip/phpstan.neon | 8 + implementation/09-wip/public/favicon.ico | Bin 0 -> 15086 bytes implementation/09-wip/public/index.php | 3 + implementation/09-wip/rector.php | 12 + implementation/09-wip/src/Action/Hello.php | 29 + implementation/09-wip/src/Action/Other.php | 24 + implementation/09-wip/src/Bootstrap.php | 101 ++ .../src/Exception/InternalServerError.php | 9 + .../09-wip/src/Exception/MethodNotAllowed.php | 9 + .../09-wip/src/Exception/NotFound.php | 9 + .../09-wip/src/Service/Time/Clock.php | 8 + .../09-wip/src/Service/Time/SystemClock.php | 12 + 80 files changed, 8471 insertions(+) create mode 100644 implementation/03/composer.json create mode 100644 implementation/03/public/favicon.ico create mode 100644 implementation/03/public/index.php create mode 100644 implementation/03/src/Bootstrap.php create mode 100644 implementation/04/composer.json create mode 100644 implementation/04/composer.lock create mode 100644 implementation/04/ecs.php create mode 100644 implementation/04/phpstan-baseline.neon create mode 100644 implementation/04/phpstan.neon create mode 100644 implementation/04/public/favicon.ico create mode 100644 implementation/04/public/index.php create mode 100644 implementation/04/rector.php create mode 100644 implementation/04/src/Bootstrap.php create mode 100644 implementation/05/composer.json create mode 100644 implementation/05/composer.lock create mode 100644 implementation/05/ecs.php create mode 100644 implementation/05/phpstan-baseline.neon create mode 100644 implementation/05/phpstan.neon create mode 100644 implementation/05/public/favicon.ico create mode 100644 implementation/05/public/index.php create mode 100644 implementation/05/rector.php create mode 100644 implementation/05/src/Bootstrap.php create mode 100644 implementation/06/composer.json create mode 100644 implementation/06/composer.lock create mode 100644 implementation/06/config/routes.php create mode 100644 implementation/06/ecs.php create mode 100644 implementation/06/phpstan-baseline.neon create mode 100644 implementation/06/phpstan.neon create mode 100644 implementation/06/public/favicon.ico create mode 100644 implementation/06/public/index.php create mode 100644 implementation/06/rector.php create mode 100644 implementation/06/src/Bootstrap.php create mode 100644 implementation/07/composer.json create mode 100644 implementation/07/composer.lock create mode 100644 implementation/07/config/routes.php create mode 100644 implementation/07/ecs.php create mode 100644 implementation/07/phpstan-baseline.neon create mode 100644 implementation/07/phpstan.neon create mode 100644 implementation/07/public/favicon.ico create mode 100644 implementation/07/public/index.php create mode 100644 implementation/07/rector.php create mode 100644 implementation/07/src/Action/Hello.php create mode 100644 implementation/07/src/Action/Other.php create mode 100644 implementation/07/src/Bootstrap.php create mode 100644 implementation/07/src/Exception/InternalServerError.php create mode 100644 implementation/07/src/Exception/MethodNotAllowed.php create mode 100644 implementation/07/src/Exception/NotFound.php create mode 100644 implementation/08/composer.json create mode 100644 implementation/08/composer.lock create mode 100644 implementation/08/config/routes.php create mode 100644 implementation/08/ecs.php create mode 100644 implementation/08/phpstan-baseline.neon create mode 100644 implementation/08/phpstan.neon create mode 100644 implementation/08/public/favicon.ico create mode 100644 implementation/08/public/index.php create mode 100644 implementation/08/rector.php create mode 100644 implementation/08/src/Action/Hello.php create mode 100644 implementation/08/src/Action/Other.php create mode 100644 implementation/08/src/Bootstrap.php create mode 100644 implementation/08/src/Exception/InternalServerError.php create mode 100644 implementation/08/src/Exception/MethodNotAllowed.php create mode 100644 implementation/08/src/Exception/NotFound.php create mode 100644 implementation/09-wip/composer.json create mode 100644 implementation/09-wip/composer.lock create mode 100644 implementation/09-wip/config/container.php create mode 100644 implementation/09-wip/config/routes.php create mode 100644 implementation/09-wip/ecs.php create mode 100644 implementation/09-wip/phpstan-baseline.neon create mode 100644 implementation/09-wip/phpstan.neon create mode 100644 implementation/09-wip/public/favicon.ico create mode 100644 implementation/09-wip/public/index.php create mode 100644 implementation/09-wip/rector.php create mode 100644 implementation/09-wip/src/Action/Hello.php create mode 100644 implementation/09-wip/src/Action/Other.php create mode 100644 implementation/09-wip/src/Bootstrap.php create mode 100644 implementation/09-wip/src/Exception/InternalServerError.php create mode 100644 implementation/09-wip/src/Exception/MethodNotAllowed.php create mode 100644 implementation/09-wip/src/Exception/NotFound.php create mode 100644 implementation/09-wip/src/Service/Time/Clock.php create mode 100644 implementation/09-wip/src/Service/Time/SystemClock.php diff --git a/implementation/03/composer.json b/implementation/03/composer.json new file mode 100644 index 0000000..a35232a --- /dev/null +++ b/implementation/03/composer.json @@ -0,0 +1,18 @@ +{ + "name": "lubiana/no-framework", + "autoload": { + "psr-4": { + "Lubiana\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "example", + "email": "test@example.com" + } + ], + "require": { + "php": ">=8.1", + "filp/whoops": "^2.14" + } +} diff --git a/implementation/03/public/favicon.ico b/implementation/03/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/03/public/index.php b/implementation/03/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/03/public/index.php @@ -0,0 +1,3 @@ +pushHandler(new PrettyPageHandler()); +} else { + $whoops->pushHandler(function (\Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +echo 'Hello World!'; diff --git a/implementation/04/composer.json b/implementation/04/composer.json new file mode 100644 index 0000000..325bc25 --- /dev/null +++ b/implementation/04/composer.json @@ -0,0 +1,45 @@ +{ + "name": "lubiana/no-framework", + "autoload": { + "psr-4": { + "Lubiana\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "example", + "email": "test@example.com" + } + ], + "require": { + "php": ">=8.1", + "filp/whoops": "^2.14" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/implementation/04/composer.lock b/implementation/04/composer.lock new file mode 100644 index 0000000..7eac750 --- /dev/null +++ b/implementation/04/composer.lock @@ -0,0 +1,872 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "92e03e1fbb1466733bb7150c4a0dec9f", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/04/ecs.php b/implementation/04/ecs.php new file mode 100644 index 0000000..03cbd02 --- /dev/null +++ b/implementation/04/ecs.php @@ -0,0 +1,89 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/implementation/04/phpstan-baseline.neon b/implementation/04/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/implementation/04/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/implementation/04/phpstan.neon b/implementation/04/phpstan.neon new file mode 100644 index 0000000..260e0ff --- /dev/null +++ b/implementation/04/phpstan.neon @@ -0,0 +1,7 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src \ No newline at end of file diff --git a/implementation/04/public/favicon.ico b/implementation/04/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/04/public/index.php b/implementation/04/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/04/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/rector.php', __DIR__ . '/ecs.php']); + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/implementation/04/src/Bootstrap.php b/implementation/04/src/Bootstrap.php new file mode 100644 index 0000000..c5db95f --- /dev/null +++ b/implementation/04/src/Bootstrap.php @@ -0,0 +1,34 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +echo 'Hello World!'; diff --git a/implementation/05/composer.json b/implementation/05/composer.json new file mode 100644 index 0000000..c880259 --- /dev/null +++ b/implementation/05/composer.json @@ -0,0 +1,46 @@ +{ + "name": "lubiana/no-framework", + "autoload": { + "psr-4": { + "Lubiana\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "example", + "email": "test@example.com" + } + ], + "require": { + "php": ">=8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.11" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/implementation/05/composer.lock b/implementation/05/composer.lock new file mode 100644 index 0000000..d03c9d5 --- /dev/null +++ b/implementation/05/composer.lock @@ -0,0 +1,1079 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "bfde95e4f108736027aa3794fc98c8cc", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-05-17T10:57:52+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/05/ecs.php b/implementation/05/ecs.php new file mode 100644 index 0000000..03cbd02 --- /dev/null +++ b/implementation/05/ecs.php @@ -0,0 +1,89 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/implementation/05/phpstan-baseline.neon b/implementation/05/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/implementation/05/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/implementation/05/phpstan.neon b/implementation/05/phpstan.neon new file mode 100644 index 0000000..260e0ff --- /dev/null +++ b/implementation/05/phpstan.neon @@ -0,0 +1,7 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src \ No newline at end of file diff --git a/implementation/05/public/favicon.ico b/implementation/05/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/05/public/index.php b/implementation/05/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/05/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/rector.php', __DIR__ . '/ecs.php']); + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/implementation/05/src/Bootstrap.php b/implementation/05/src/Bootstrap.php new file mode 100644 index 0000000..036a954 --- /dev/null +++ b/implementation/05/src/Bootstrap.php @@ -0,0 +1,63 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +$request = ServerRequestFactory::fromGlobals(); +$response = new Response; +$response->getBody() + ->write('Hello World! '); +$response->getBody() + ->write('The Uri is: ' . $request->getUri()->getPath()); + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/06/composer.json b/implementation/06/composer.json new file mode 100644 index 0000000..61d0e38 --- /dev/null +++ b/implementation/06/composer.json @@ -0,0 +1,47 @@ +{ + "name": "lubiana/no-framework", + "autoload": { + "psr-4": { + "Lubiana\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "example", + "email": "test@example.com" + } + ], + "require": { + "php": ">=8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.11", + "nikic/fast-route": "^1.3" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/implementation/06/composer.lock b/implementation/06/composer.lock new file mode 100644 index 0000000..c737c41 --- /dev/null +++ b/implementation/06/composer.lock @@ -0,0 +1,1129 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "226d7e07ad29221e0052123c575e35df", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-05-17T10:57:52+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/06/config/routes.php b/implementation/06/config/routes.php new file mode 100644 index 0000000..6522454 --- /dev/null +++ b/implementation/06/config/routes.php @@ -0,0 +1,21 @@ +addRoute('GET', '/hello[/{name}]', function (ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new Response)->withStatus(200); + $response->getBody() + ->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/other', function (ServerRequestInterface $request) { + $response = (new Response)->withStatus(200); + $response->getBody() + ->write('This works too!'); + return $response; + }); +}; diff --git a/implementation/06/ecs.php b/implementation/06/ecs.php new file mode 100644 index 0000000..6742326 --- /dev/null +++ b/implementation/06/ecs.php @@ -0,0 +1,89 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/implementation/06/phpstan-baseline.neon b/implementation/06/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/implementation/06/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/implementation/06/phpstan.neon b/implementation/06/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/06/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/06/public/favicon.ico b/implementation/06/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/06/public/index.php b/implementation/06/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/06/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/implementation/06/src/Bootstrap.php b/implementation/06/src/Bootstrap.php new file mode 100644 index 0000000..e2944a3 --- /dev/null +++ b/implementation/06/src/Bootstrap.php @@ -0,0 +1,97 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +$request = ServerRequestFactory::fromGlobals(); +$response = new Response; +$response->getBody() + ->write('Hello World! '); +$response->getBody() + ->write('The Uri is: ' . $request->getUri()->getPath()); + + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); + +switch ($routeInfo[0]) { + case Dispatcher::METHOD_NOT_ALLOWED: + $response = (new Response)->withStatus(405); + $response->getBody() + ->write('Method not allowed'); + $response = $response->withStatus(405); + break; + case Dispatcher::FOUND: + $handler = $routeInfo[1]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + /** @var ResponseInterface $response */ + $response = call_user_func($handler, $request); + break; + case Dispatcher::NOT_FOUND: + default: + $response = (new Response)->withStatus(404); + $response->getBody() + ->write('Not Found!'); + break; +} + + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/07/composer.json b/implementation/07/composer.json new file mode 100644 index 0000000..4f79b97 --- /dev/null +++ b/implementation/07/composer.json @@ -0,0 +1,48 @@ +{ + "name": "lubiana/no-framework", + "autoload": { + "psr-4": { + "Lubiana\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "example", + "email": "test@example.com" + } + ], + "require": { + "php": ">=8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.11", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/implementation/07/composer.lock b/implementation/07/composer.lock new file mode 100644 index 0000000..a9f5b96 --- /dev/null +++ b/implementation/07/composer.lock @@ -0,0 +1,1186 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "5f281d245eeab688c1904bf024aeff4f", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-05-17T10:57:52+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/07/config/routes.php b/implementation/07/config/routes.php new file mode 100644 index 0000000..c9b632d --- /dev/null +++ b/implementation/07/config/routes.php @@ -0,0 +1,10 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/other', Other::class); +}; diff --git a/implementation/07/ecs.php b/implementation/07/ecs.php new file mode 100644 index 0000000..6742326 --- /dev/null +++ b/implementation/07/ecs.php @@ -0,0 +1,89 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/implementation/07/phpstan-baseline.neon b/implementation/07/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/implementation/07/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/implementation/07/phpstan.neon b/implementation/07/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/07/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/07/public/favicon.ico b/implementation/07/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/07/public/index.php b/implementation/07/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/07/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/implementation/07/src/Action/Hello.php b/implementation/07/src/Action/Hello.php new file mode 100644 index 0000000..3deb900 --- /dev/null +++ b/implementation/07/src/Action/Hello.php @@ -0,0 +1,20 @@ +getAttribute('name', 'Stranger'); + $response = (new Response)->withStatus(200); + $response->getBody() + ->write('Hello ' . $name . '!'); + return $response; + } +} diff --git a/implementation/07/src/Action/Other.php b/implementation/07/src/Action/Other.php new file mode 100644 index 0000000..5481073 --- /dev/null +++ b/implementation/07/src/Action/Other.php @@ -0,0 +1,19 @@ +withStatus(200); + $response->getBody() + ->write('This works too!'); + return $response; + } +} diff --git a/implementation/07/src/Bootstrap.php b/implementation/07/src/Bootstrap.php new file mode 100644 index 0000000..6317c47 --- /dev/null +++ b/implementation/07/src/Bootstrap.php @@ -0,0 +1,101 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +$request = ServerRequestFactory::fromGlobals(); + + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + $className = $routeInfo[1]; + $handler = new $className; + assert($handler instanceof RequestHandlerInterface); + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody() + ->write('Method not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody() + ->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} + + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/07/src/Exception/InternalServerError.php b/implementation/07/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/07/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +=8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.11", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/implementation/08/composer.lock b/implementation/08/composer.lock new file mode 100644 index 0000000..a9f5b96 --- /dev/null +++ b/implementation/08/composer.lock @@ -0,0 +1,1186 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "5f281d245eeab688c1904bf024aeff4f", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-05-17T10:57:52+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/08/config/routes.php b/implementation/08/config/routes.php new file mode 100644 index 0000000..c9b632d --- /dev/null +++ b/implementation/08/config/routes.php @@ -0,0 +1,10 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/other', Other::class); +}; diff --git a/implementation/08/ecs.php b/implementation/08/ecs.php new file mode 100644 index 0000000..6742326 --- /dev/null +++ b/implementation/08/ecs.php @@ -0,0 +1,89 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/implementation/08/phpstan-baseline.neon b/implementation/08/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/implementation/08/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/implementation/08/phpstan.neon b/implementation/08/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/08/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/08/public/favicon.ico b/implementation/08/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/08/public/index.php b/implementation/08/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/08/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/implementation/08/src/Action/Hello.php b/implementation/08/src/Action/Hello.php new file mode 100644 index 0000000..696421b --- /dev/null +++ b/implementation/08/src/Action/Hello.php @@ -0,0 +1,25 @@ +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + + return $this->response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/08/src/Action/Other.php b/implementation/08/src/Action/Other.php new file mode 100644 index 0000000..c42c74b --- /dev/null +++ b/implementation/08/src/Action/Other.php @@ -0,0 +1,24 @@ +response->getBody(); + + $body->write('This works too!'); + + return $this->response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/08/src/Bootstrap.php b/implementation/08/src/Bootstrap.php new file mode 100644 index 0000000..98a3952 --- /dev/null +++ b/implementation/08/src/Bootstrap.php @@ -0,0 +1,101 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +$request = ServerRequestFactory::fromGlobals(); +$response = new Response; + + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + $className = $routeInfo[1]; + $handler = new $className($response); + assert($handler instanceof RequestHandlerInterface); + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody() + ->write('Method not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody() + ->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/08/src/Exception/InternalServerError.php b/implementation/08/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/08/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +=8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.11", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/implementation/09-wip/composer.lock b/implementation/09-wip/composer.lock new file mode 100644 index 0000000..a9f5b96 --- /dev/null +++ b/implementation/09-wip/composer.lock @@ -0,0 +1,1186 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "5f281d245eeab688c1904bf024aeff4f", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-05-17T10:57:52+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/09-wip/config/container.php b/implementation/09-wip/config/container.php new file mode 100644 index 0000000..87eb322 --- /dev/null +++ b/implementation/09-wip/config/container.php @@ -0,0 +1,9 @@ + fn () => new \Lubian\NoFramework\Action\Hello($response, $clock), + \Lubian\NoFramework\Action\Other::class => fn () => new \Lubian\NoFramework\Action\Other($response), +]; diff --git a/implementation/09-wip/config/routes.php b/implementation/09-wip/config/routes.php new file mode 100644 index 0000000..c9b632d --- /dev/null +++ b/implementation/09-wip/config/routes.php @@ -0,0 +1,10 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/other', Other::class); +}; diff --git a/implementation/09-wip/ecs.php b/implementation/09-wip/ecs.php new file mode 100644 index 0000000..6742326 --- /dev/null +++ b/implementation/09-wip/ecs.php @@ -0,0 +1,89 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/implementation/09-wip/phpstan-baseline.neon b/implementation/09-wip/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/implementation/09-wip/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/implementation/09-wip/phpstan.neon b/implementation/09-wip/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/09-wip/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/09-wip/public/favicon.ico b/implementation/09-wip/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/09-wip/public/index.php b/implementation/09-wip/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/09-wip/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/implementation/09-wip/src/Action/Hello.php b/implementation/09-wip/src/Action/Hello.php new file mode 100644 index 0000000..6b4d24a --- /dev/null +++ b/implementation/09-wip/src/Action/Hello.php @@ -0,0 +1,29 @@ +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $time = $this->clock->now()->format('H:i:s'); + + $body->write('Hello ' . $name . '!
'); + $body->write('The Time is: ' . $time); + + return $this->response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/09-wip/src/Action/Other.php b/implementation/09-wip/src/Action/Other.php new file mode 100644 index 0000000..c42c74b --- /dev/null +++ b/implementation/09-wip/src/Action/Other.php @@ -0,0 +1,24 @@ +response->getBody(); + + $body->write('This works too!'); + + return $this->response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/09-wip/src/Bootstrap.php b/implementation/09-wip/src/Bootstrap.php new file mode 100644 index 0000000..98a3952 --- /dev/null +++ b/implementation/09-wip/src/Bootstrap.php @@ -0,0 +1,101 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +$request = ServerRequestFactory::fromGlobals(); +$response = new Response; + + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + $className = $routeInfo[1]; + $handler = new $className($response); + assert($handler instanceof RequestHandlerInterface); + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody() + ->write('Method not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody() + ->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/09-wip/src/Exception/InternalServerError.php b/implementation/09-wip/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/09-wip/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ + Date: Fri, 20 May 2022 09:28:47 +0200 Subject: [PATCH 140/314] wip: rewrite di chapter --- 09-dependency-injector.md | 10 + app/composer.json | 49 + app/composer.lock | 1239 +++++++++++++++++++++ app/config/container.php | 37 + app/config/routes.php | 10 + app/ecs.php | 89 ++ app/phpstan-baseline.neon | 6 + app/phpstan.neon | 8 + app/public/index.php | 3 + app/rector.php | 12 + app/src/Action/Hello.php | 29 + app/src/Action/Other.php | 24 + app/src/Bootstrap.php | 104 ++ app/src/Exception/InternalServerError.php | 9 + app/src/Exception/MethodNotAllowed.php | 9 + app/src/Exception/NotFound.php | 9 + app/src/Service/Time/Clock.php | 8 + app/src/Service/Time/SystemClock.php | 12 + 18 files changed, 1667 insertions(+) create mode 100644 app/composer.json create mode 100644 app/composer.lock create mode 100644 app/config/container.php create mode 100644 app/config/routes.php create mode 100644 app/ecs.php create mode 100644 app/phpstan-baseline.neon create mode 100644 app/phpstan.neon create mode 100644 app/public/index.php create mode 100644 app/rector.php create mode 100644 app/src/Action/Hello.php create mode 100644 app/src/Action/Other.php create mode 100644 app/src/Bootstrap.php create mode 100644 app/src/Exception/InternalServerError.php create mode 100644 app/src/Exception/MethodNotAllowed.php create mode 100644 app/src/Exception/NotFound.php create mode 100644 app/src/Service/Time/Clock.php create mode 100644 app/src/Service/Time/SystemClock.php diff --git a/09-dependency-injector.md b/09-dependency-injector.md index cfbeb7a..67d79e3 100644 --- a/09-dependency-injector.md +++ b/09-dependency-injector.md @@ -2,6 +2,16 @@ ### Dependency Injector +In the last chapter we rewrote our Actions to require the response-objet as a constructor parameter, and provided it +in the dispatcher section of our `Bootstrap.php`. As we only have one dependency this works really fine, but if we have +different classes with different dependencies our bootstrap file gets complicated quite quickly. Lets look at an example +to explain the problem and work on a solution. + +#### Adding a clock service + +Lets assume that we want to show the current time in our Hello action. We could easily just call use one of the many +ways to get the current time directly in the handle-method, but maybe we want to make that configurable and + A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when the class is instantiated. diff --git a/app/composer.json b/app/composer.json new file mode 100644 index 0000000..980b03b --- /dev/null +++ b/app/composer.json @@ -0,0 +1,49 @@ +{ + "name": "lubian/no-framework", + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "example", + "email": "test@example.com" + } + ], + "require": { + "php": ">=8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.11", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "psr/container": "^2.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/app/composer.lock b/app/composer.lock new file mode 100644 index 0000000..18e9e6b --- /dev/null +++ b/app/composer.lock @@ -0,0 +1,1239 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "1ccaabdd7944ba2f12098b7b2f1c91c2", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-05-17T10:57:52+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/app/config/container.php b/app/config/container.php new file mode 100644 index 0000000..55766cb --- /dev/null +++ b/app/config/container.php @@ -0,0 +1,37 @@ +services = [ + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), + \Psr\Http\Message\ResponseInterface::class => fn () => new \Laminas\Diactoros\Response(), + \FastRoute\Dispatcher::class => fn () => \FastRoute\simpleDispatcher(require __DIR__ . '/routes.php'), + \Lubian\NoFramework\Service\Time\Clock::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClock(), + \Lubian\NoFramework\Action\Hello::class => fn () => new \Lubian\NoFramework\Action\Hello( + $this->get(\Psr\Http\Message\ResponseInterface::class), + $this->get(\Lubian\NoFramework\Service\Time\Clock::class) + ), + \Lubian\NoFramework\Action\Other::class => fn () => new \Lubian\NoFramework\Action\Other( + $this->get(\Psr\Http\Message\ResponseInterface::class) + ), + ]; + } + + public function get(string $id) + { + if (! $this->has($id)) { + throw new class () extends \Exception implements \Psr\Container\NotFoundExceptionInterface { + }; + } + return $this->services[$id](); + } + + public function has(string $id): bool + { + return array_key_exists($id, $this->services); + } +}; diff --git a/app/config/routes.php b/app/config/routes.php new file mode 100644 index 0000000..c9b632d --- /dev/null +++ b/app/config/routes.php @@ -0,0 +1,10 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/other', Other::class); +}; diff --git a/app/ecs.php b/app/ecs.php new file mode 100644 index 0000000..6742326 --- /dev/null +++ b/app/ecs.php @@ -0,0 +1,89 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/app/phpstan-baseline.neon b/app/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/app/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/app/phpstan.neon b/app/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/app/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/app/public/index.php b/app/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/app/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/app/src/Action/Hello.php b/app/src/Action/Hello.php new file mode 100644 index 0000000..6b4d24a --- /dev/null +++ b/app/src/Action/Hello.php @@ -0,0 +1,29 @@ +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $time = $this->clock->now()->format('H:i:s'); + + $body->write('Hello ' . $name . '!
'); + $body->write('The Time is: ' . $time); + + return $this->response->withBody($body) + ->withStatus(200); + } +} diff --git a/app/src/Action/Other.php b/app/src/Action/Other.php new file mode 100644 index 0000000..c42c74b --- /dev/null +++ b/app/src/Action/Other.php @@ -0,0 +1,24 @@ +response->getBody(); + + $body->write('This works too!'); + + return $this->response->withBody($body) + ->withStatus(200); + } +} diff --git a/app/src/Bootstrap.php b/app/src/Bootstrap.php new file mode 100644 index 0000000..023c8c0 --- /dev/null +++ b/app/src/Bootstrap.php @@ -0,0 +1,104 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +$container = require __DIR__ . '/../config/container.php'; +assert($container instanceof ContainerInterface); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$dispatcher = $container->get(Dispatcher::class); +assert($dispatcher instanceof Dispatcher); + + +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + $className = $routeInfo[1]; + $handler = $container->get($className); + assert($handler instanceof RequestHandlerInterface); + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody() + ->write('Method not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody() + ->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/app/src/Exception/InternalServerError.php b/app/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/app/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ + Date: Sat, 21 May 2022 00:21:15 +0200 Subject: [PATCH 141/314] explain implementation of ad-hoc depencency container --- 09-dependency-injector.md | 193 +++++++++++++++++++++++++++++++++++++- app/src/Action/Hello.php | 6 +- app/src/Bootstrap.php | 2 +- 3 files changed, 197 insertions(+), 4 deletions(-) diff --git a/09-dependency-injector.md b/09-dependency-injector.md index 67d79e3..8b0b5eb 100644 --- a/09-dependency-injector.md +++ b/09-dependency-injector.md @@ -10,12 +10,201 @@ to explain the problem and work on a solution. #### Adding a clock service Lets assume that we want to show the current time in our Hello action. We could easily just call use one of the many -ways to get the current time directly in the handle-method, but maybe we want to make that configurable and +ways to get the current time directly in the handle-method, but lets create a separate class and interface for that so +we can later configure and switch our implementation. + +We need a new 'Service\Time' namespace, so lets first create the folder in our 'src' directory 'src/Service/Time'. +There we place a Clock.php interface and a SystemClock.php implementation: + + +The Clock.php interface: +```php +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $time = $this->clock->now()->format('H:i:s'); + + $body->write('Hello ' . $name . '!
'); + $body->write('The Time is: ' . $time); + + return $this->response->withBody($body) + ->withStatus(200); + } +} +``` + +But if we try to access the corresponding route in the webbrowser we get an error: +> Too few arguments to function Lubian\NoFramework\Action\Hello::__construct(), 1 passed in /home/lubiana/PhpstormProjects/no-framework/app/src/Bootstrap.php on line 62 and exactly 2 expected + +Our current problem is, that we have two Actions defined, which both have different constructor requirements. That means, +that we need to have some code in our Application, that creates our Action Objects and takes care of injection all the +needed dependencies. + +This code is called a Dependency Injector. If you want you can read [this](https://afilina.com/learn/design/dependency-injection) +great blogpost about that topic, which I highly recommend. + +Lets build our own Dependency Injector to make our application work again. + +As a starting point we are going to take a look at the [Container Interface])(https://www.php-fig.org/psr/psr-11/) that +is widely adopted in the PHP-World. + +#### Building a dependency container + +**Short Disclaimer:** *Although it would be fun to write our own great implementation of this interface with everything that +is needed for modern php development I will take a shortcut here and implement very reduced version to show you the +basic concept.* + +The `Pst\Container\ContainerIterface` defines two methods: + +* has($id): bool + returns true if the container can provide a value for a given ID +* get($id): mixed + returns some kind of value that is registered in the container for the given ID + +I mostly define an Interface or a fully qualified classname as an ID. That way I can query the container for +the Clock interface or an Action class and get an object of that class or an object implementing the given Interface. + +For the sake of this tutorial we will put a new file in our config folder that returns an anonymous class implementing +the containerinterface. + +In this class we will configure all services required for our application and make them accessible via the get($id) +method. + +`config/container.php`: +```php +services = [ + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), + \Psr\Http\Message\ResponseInterface::class => fn () => new \Laminas\Diactoros\Response(), + \FastRoute\Dispatcher::class => fn () => \FastRoute\simpleDispatcher(require __DIR__ . '/routes.php'), + \Lubian\NoFramework\Service\Time\Clock::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClock(), + \Lubian\NoFramework\Action\Hello::class => fn () => new \Lubian\NoFramework\Action\Hello( + $this->get(\Psr\Http\Message\ResponseInterface::class), + $this->get(\Lubian\NoFramework\Service\Time\Clock::class) + ), + \Lubian\NoFramework\Action\Other::class => fn () => new \Lubian\NoFramework\Action\Other( + $this->get(\Psr\Http\Message\ResponseInterface::class) + ), + ]; + } + + public function get(string $id) + { + if (! $this->has($id)) { + throw new class () extends \Exception implements \Psr\Container\NotFoundExceptionInterface { + }; + } + return $this->services[$id](); + } + + public function has(string $id): bool + { + return array_key_exists($id, $this->services); + } +}; +``` + +Here I have declared a services array, that has a class- or interfacename as the keys, and the values are short +closures that return an Object of the defined class or interface. The `has` method simply checks if the given id is +defined in our services array, and the `get` method calls the closure defined in the array for the given id key and then +returns the result of that closure. + +To use the container we need to update our Bootstrap.php. Firstly we need to get an instance of our container, and then +use that to create our Request-Object as well as the Dispatcher. So remove the manual instantion of those objects and +replace that with the following code: + +```php +$container = require __DIR__ . '/../config/container.php'; +assert($container instanceof \Psr\Container\ContainerInterface); + +$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); +assert($request instanceof \Psr\Http\Message\ServerRequestInterface); + +$dispatcher = $container->get(FastRoute\Dispatcher::class); +assert($dispatcher instanceof \FastRoute\Dispatcher); +``` + +In the Dispatcher switch block we manually build our handler object with this two lines: + + +```php +$handler = new $className($response); +assert($handler instanceof RequestHandlerInterface); +``` + +Instead of manually creating the Handler-Instance we are going to kindly ask the Container to build it for us: + +```php +$handler = $container->get($className); +assert($handler instanceof RequestHandlerInterface); +``` + +If you now open the `/hello` route in your browser everything should work again! + +#### Using Autowiring + + A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when the class is instantiated. -Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work +Again the FIG has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). diff --git a/app/src/Action/Hello.php b/app/src/Action/Hello.php index 6b4d24a..d107411 100644 --- a/app/src/Action/Hello.php +++ b/app/src/Action/Hello.php @@ -2,6 +2,7 @@ namespace Lubian\NoFramework\Action; + use Lubian\NoFramework\Service\Time\Clock; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -9,7 +10,10 @@ use Psr\Http\Server\RequestHandlerInterface; final class Hello implements RequestHandlerInterface { - public function __construct(private readonly ResponseInterface $response, private readonly Clock $clock) + public function __construct( + private readonly ResponseInterface $response, + private readonly Clock $clock + ) { } diff --git a/app/src/Bootstrap.php b/app/src/Bootstrap.php index 023c8c0..4fe4a67 100644 --- a/app/src/Bootstrap.php +++ b/app/src/Bootstrap.php @@ -59,7 +59,7 @@ try { switch ($routeInfo[0]) { case Dispatcher::FOUND: $className = $routeInfo[1]; - $handler = $container->get($className); + $handler = new $className($response); assert($handler instanceof RequestHandlerInterface); foreach ($routeInfo[2] as $attributeName => $attributeValue) { $request = $request->withAttribute($attributeName, $attributeValue); From 5dc8ad38dd79bef795d2ad06fc6d3a55307095e1 Mon Sep 17 00:00:00 2001 From: lubiana Date: Sat, 21 May 2022 00:53:33 +0200 Subject: [PATCH 142/314] explain implementation of ad-hoc depencency container --- 09-dependency-injector.md | 217 +++-------------------- app/composer.json | 3 +- app/composer.lock | 255 +++++++++++++++++++++++++-- app/config/container.php | 48 ++--- app/src/Action/Hello.php | 7 +- app/src/Bootstrap.php | 2 +- app/src/Service/Time/Clock.php | 6 +- app/src/Service/Time/SystemClock.php | 9 +- 8 files changed, 297 insertions(+), 250 deletions(-) diff --git a/09-dependency-injector.md b/09-dependency-injector.md index 8b0b5eb..060a09f 100644 --- a/09-dependency-injector.md +++ b/09-dependency-injector.md @@ -119,6 +119,10 @@ the containerinterface. In this class we will configure all services required for our application and make them accessible via the get($id) method. +p +Before we can implement the interface we need to install its definition with composer `composer require "psr/container:^1.0"`. +now we can create a file with a Class that implements that interface. + `config/container.php`: ```php addDefinitions([ - \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), + \Psr\Http\Message\ResponseInterface::class => fn () => new \Laminas\Diactoros\Response(), + \FastRoute\Dispatcher::class => fn () => \FastRoute\simpleDispatcher(require __DIR__ . '/routes.php'), + \Lubian\NoFramework\Service\Time\Clock::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClock(), ]); return $builder->build(); ``` -In this file we create a containerbuilder, add some definitions to it and return the container. -As the container supports autowiring we only need to define services where we want to use a specific implementation of -an interface. +As the PHP-DI container that is return by the `$builder->build()` method implements the same container interface as our +previously used ad-hoc container we won't need to update the our Bootstrap file and everything still works. -In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to -tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second -exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. - -Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), -as we will use that extensively. - -Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally -to create our Handler-Object - -```php -$container = require __DIR__ . '/../config/dependencies.php'; -assert($container instanceof \Psr\Container\ContainerInterface); - -$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); -assert($request instanceof \Psr\Http\Message\ServerRequestInterface); -``` - -The other part that has to be changed is the dispatching of the route. Before you had the following code: - -```php -$className = $routeInfo[1]; -$handler = new $className($response); -assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Change that to the following: - -```php -/** @var RequestHandlerInterface $handler */ -$className = $routeInfo[1]; -$handler = $container->get($className); -assert($handler instanceof RequestHandlerInterface); -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Make sure to use the container fetch the response object in the catch blocks as well: - -```php -} catch (MethodNotAllowed) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(404); - $response->getBody()->write('Not Found'); -} -``` - -Now all your controller constructor dependencies will be automatically resolved with PHP-DI. - -We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons -in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call -`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we -want to work with a different date in a test. - -Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation -that creates us a DateTimeImmutable object with the current date and time. - -src/Service/Time/Now.php: -```php -namespace Lubian\NoFramework\Service\Time; - -interface Now -{ - public function __invoke(): \DateTimeImmutable; -} -``` -src/Service/Time/SystemClockNow.php: -```php -namespace Lubian\NoFramework\Service\Time; - -final class SystemClockNow implements Now -{ - - public function __invoke(): \DateTimeImmutable - { - return new \DateTimeImmutable; - } -} -``` -If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and -update the handle-method to use the new class property: - -```php -getAttribute('name', 'Stranger'); - $nowAsString = ($this->now)()->format('H:i:s'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowAsString); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI -automatically figures out what classes are requested in the constructor and tries to create the objects needed. - -But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred -S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: - -```php - public function __construct( - private ResponseInterface $response, - private Now $now, - ) -``` - -When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation -should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: - -```php -\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), -``` - -we could also use the PHP-DI create method to delegate the object creation to the container implementation: -```php -\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), -``` - -this way the container can try to resolve any dependencies that the class might have internally, but prefer the other -method because we are not depending on this specific dependency injection implementation. - -Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are -requesting the Hello action. - -If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As -we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way -we wont get any warnings for this particular issue, but for any other issues we add to our code. - -Update the phpstan.neon file to include a "baseline" file: - -``` -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` - -if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and -ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just -'baseline' [<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/app/composer.json b/app/composer.json index 980b03b..335b122 100644 --- a/app/composer.json +++ b/app/composer.json @@ -17,7 +17,8 @@ "laminas/laminas-diactoros": "^2.11", "nikic/fast-route": "^1.3", "psr/http-server-handler": "^1.0", - "psr/container": "^2.0" + "psr/container": "^1.0", + "php-di/php-di": "^6.4" }, "require-dev": { "phpstan/phpstan": "^1.6", diff --git a/app/composer.lock b/app/composer.lock index 18e9e6b..386eb40 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1ccaabdd7944ba2f12098b7b2f1c91c2", + "content-hash": "0b6833b8fa6869bd212824769648e667", "packages": [ { "name": "filp/whoops", @@ -176,6 +176,65 @@ ], "time": "2022-05-17T10:57:52+00:00" }, + { + "name": "laravel/serializable-closure", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "pestphp/pest": "^1.18", + "phpstan/phpstan": "^0.12.98", + "symfony/var-dumper": "^5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2022-05-16T17:09:47+00:00" + }, { "name": "nikic/fast-route", "version": "v1.3.0", @@ -227,28 +286,196 @@ "time": "2018-02-13T20:26:39+00:00" }, { - "name": "psr/container", - "version": "2.0.2", + "name": "php-di/invoker", + "version": "2.3.3", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.4.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "shasum": "" + }, + "require": { + "laravel/serializable-closure": "^1.0", + "php": ">=7.4.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.10", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.11.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^9.5" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.4.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2022-04-09T16:46:38+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", "shasum": "" }, "require": { "php": ">=7.4.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, "autoload": { "psr-4": { "Psr\\Container\\": "src/" @@ -275,9 +502,9 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" + "source": "https://github.com/php-fig/container/tree/1.1.2" }, - "time": "2021-11-05T16:47:00+00:00" + "time": "2021-11-05T16:50:12+00:00" }, { "name": "psr/http-factory", diff --git a/app/config/container.php b/app/config/container.php index 55766cb..ceb20dc 100644 --- a/app/config/container.php +++ b/app/config/container.php @@ -1,37 +1,23 @@ services = [ - \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), - \Psr\Http\Message\ResponseInterface::class => fn () => new \Laminas\Diactoros\Response(), - \FastRoute\Dispatcher::class => fn () => \FastRoute\simpleDispatcher(require __DIR__ . '/routes.php'), - \Lubian\NoFramework\Service\Time\Clock::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClock(), - \Lubian\NoFramework\Action\Hello::class => fn () => new \Lubian\NoFramework\Action\Hello( - $this->get(\Psr\Http\Message\ResponseInterface::class), - $this->get(\Lubian\NoFramework\Service\Time\Clock::class) - ), - \Lubian\NoFramework\Action\Other::class => fn () => new \Lubian\NoFramework\Action\Other( - $this->get(\Psr\Http\Message\ResponseInterface::class) - ), - ]; - } +$builder = new ContainerBuilder; - public function get(string $id) - { - if (! $this->has($id)) { - throw new class () extends \Exception implements \Psr\Container\NotFoundExceptionInterface { - }; - } - return $this->services[$id](); - } +$builder->addDefinitions([ + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + ResponseInterface::class => fn () => new Response, + Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + Clock::class => fn () => new SystemClock, +]); - public function has(string $id): bool - { - return array_key_exists($id, $this->services); - } -}; +return $builder->build(); diff --git a/app/src/Action/Hello.php b/app/src/Action/Hello.php index d107411..ec2e00c 100644 --- a/app/src/Action/Hello.php +++ b/app/src/Action/Hello.php @@ -2,7 +2,6 @@ namespace Lubian\NoFramework\Action; - use Lubian\NoFramework\Service\Time\Clock; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -13,8 +12,7 @@ final class Hello implements RequestHandlerInterface public function __construct( private readonly ResponseInterface $response, private readonly Clock $clock - ) - { + ) { } public function handle(ServerRequestInterface $request): ResponseInterface @@ -22,7 +20,8 @@ final class Hello implements RequestHandlerInterface $name = $request->getAttribute('name', 'Stranger'); $body = $this->response->getBody(); - $time = $this->clock->now()->format('H:i:s'); + $time = $this->clock->now() + ->format('H:i:s'); $body->write('Hello ' . $name . '!
'); $body->write('The Time is: ' . $time); diff --git a/app/src/Bootstrap.php b/app/src/Bootstrap.php index 4fe4a67..023c8c0 100644 --- a/app/src/Bootstrap.php +++ b/app/src/Bootstrap.php @@ -59,7 +59,7 @@ try { switch ($routeInfo[0]) { case Dispatcher::FOUND: $className = $routeInfo[1]; - $handler = new $className($response); + $handler = $container->get($className); assert($handler instanceof RequestHandlerInterface); foreach ($routeInfo[2] as $attributeName => $attributeValue) { $request = $request->withAttribute($attributeName, $attributeValue); diff --git a/app/src/Service/Time/Clock.php b/app/src/Service/Time/Clock.php index 50897dc..204ccb4 100644 --- a/app/src/Service/Time/Clock.php +++ b/app/src/Service/Time/Clock.php @@ -2,7 +2,9 @@ namespace Lubian\NoFramework\Service\Time; +use DateTimeImmutable; + interface Clock { - public function now(): \DateTimeImmutable; -} \ No newline at end of file + public function now(): DateTimeImmutable; +} diff --git a/app/src/Service/Time/SystemClock.php b/app/src/Service/Time/SystemClock.php index f0d0d0a..705d81f 100644 --- a/app/src/Service/Time/SystemClock.php +++ b/app/src/Service/Time/SystemClock.php @@ -2,11 +2,12 @@ namespace Lubian\NoFramework\Service\Time; +use DateTimeImmutable; + final class SystemClock implements Clock { - public function now(): \DateTimeImmutable + public function now(): DateTimeImmutable { - return new \DateTimeImmutable(); + return new DateTimeImmutable; } - -} \ No newline at end of file +} From 26aa4b1502723238f513fd6319313efe08fdcbbc Mon Sep 17 00:00:00 2001 From: lubiana Date: Sat, 21 May 2022 00:54:36 +0200 Subject: [PATCH 143/314] rename implementation 09-wip directory --- implementation/{09-wip => 09}/composer.json | 0 implementation/{09-wip => 09}/composer.lock | 0 implementation/{09-wip => 09}/config/container.php | 0 implementation/{09-wip => 09}/config/routes.php | 0 implementation/{09-wip => 09}/ecs.php | 0 implementation/{09-wip => 09}/phpstan-baseline.neon | 0 implementation/{09-wip => 09}/phpstan.neon | 0 implementation/{09-wip => 09}/public/favicon.ico | Bin implementation/{09-wip => 09}/public/index.php | 0 implementation/{09-wip => 09}/rector.php | 0 implementation/{09-wip => 09}/src/Action/Hello.php | 0 implementation/{09-wip => 09}/src/Action/Other.php | 0 implementation/{09-wip => 09}/src/Bootstrap.php | 0 .../src/Exception/InternalServerError.php | 0 .../src/Exception/MethodNotAllowed.php | 0 .../{09-wip => 09}/src/Exception/NotFound.php | 0 .../{09-wip => 09}/src/Service/Time/Clock.php | 0 .../{09-wip => 09}/src/Service/Time/SystemClock.php | 0 18 files changed, 0 insertions(+), 0 deletions(-) rename implementation/{09-wip => 09}/composer.json (100%) rename implementation/{09-wip => 09}/composer.lock (100%) rename implementation/{09-wip => 09}/config/container.php (100%) rename implementation/{09-wip => 09}/config/routes.php (100%) rename implementation/{09-wip => 09}/ecs.php (100%) rename implementation/{09-wip => 09}/phpstan-baseline.neon (100%) rename implementation/{09-wip => 09}/phpstan.neon (100%) rename implementation/{09-wip => 09}/public/favicon.ico (100%) rename implementation/{09-wip => 09}/public/index.php (100%) rename implementation/{09-wip => 09}/rector.php (100%) rename implementation/{09-wip => 09}/src/Action/Hello.php (100%) rename implementation/{09-wip => 09}/src/Action/Other.php (100%) rename implementation/{09-wip => 09}/src/Bootstrap.php (100%) rename implementation/{09-wip => 09}/src/Exception/InternalServerError.php (100%) rename implementation/{09-wip => 09}/src/Exception/MethodNotAllowed.php (100%) rename implementation/{09-wip => 09}/src/Exception/NotFound.php (100%) rename implementation/{09-wip => 09}/src/Service/Time/Clock.php (100%) rename implementation/{09-wip => 09}/src/Service/Time/SystemClock.php (100%) diff --git a/implementation/09-wip/composer.json b/implementation/09/composer.json similarity index 100% rename from implementation/09-wip/composer.json rename to implementation/09/composer.json diff --git a/implementation/09-wip/composer.lock b/implementation/09/composer.lock similarity index 100% rename from implementation/09-wip/composer.lock rename to implementation/09/composer.lock diff --git a/implementation/09-wip/config/container.php b/implementation/09/config/container.php similarity index 100% rename from implementation/09-wip/config/container.php rename to implementation/09/config/container.php diff --git a/implementation/09-wip/config/routes.php b/implementation/09/config/routes.php similarity index 100% rename from implementation/09-wip/config/routes.php rename to implementation/09/config/routes.php diff --git a/implementation/09-wip/ecs.php b/implementation/09/ecs.php similarity index 100% rename from implementation/09-wip/ecs.php rename to implementation/09/ecs.php diff --git a/implementation/09-wip/phpstan-baseline.neon b/implementation/09/phpstan-baseline.neon similarity index 100% rename from implementation/09-wip/phpstan-baseline.neon rename to implementation/09/phpstan-baseline.neon diff --git a/implementation/09-wip/phpstan.neon b/implementation/09/phpstan.neon similarity index 100% rename from implementation/09-wip/phpstan.neon rename to implementation/09/phpstan.neon diff --git a/implementation/09-wip/public/favicon.ico b/implementation/09/public/favicon.ico similarity index 100% rename from implementation/09-wip/public/favicon.ico rename to implementation/09/public/favicon.ico diff --git a/implementation/09-wip/public/index.php b/implementation/09/public/index.php similarity index 100% rename from implementation/09-wip/public/index.php rename to implementation/09/public/index.php diff --git a/implementation/09-wip/rector.php b/implementation/09/rector.php similarity index 100% rename from implementation/09-wip/rector.php rename to implementation/09/rector.php diff --git a/implementation/09-wip/src/Action/Hello.php b/implementation/09/src/Action/Hello.php similarity index 100% rename from implementation/09-wip/src/Action/Hello.php rename to implementation/09/src/Action/Hello.php diff --git a/implementation/09-wip/src/Action/Other.php b/implementation/09/src/Action/Other.php similarity index 100% rename from implementation/09-wip/src/Action/Other.php rename to implementation/09/src/Action/Other.php diff --git a/implementation/09-wip/src/Bootstrap.php b/implementation/09/src/Bootstrap.php similarity index 100% rename from implementation/09-wip/src/Bootstrap.php rename to implementation/09/src/Bootstrap.php diff --git a/implementation/09-wip/src/Exception/InternalServerError.php b/implementation/09/src/Exception/InternalServerError.php similarity index 100% rename from implementation/09-wip/src/Exception/InternalServerError.php rename to implementation/09/src/Exception/InternalServerError.php diff --git a/implementation/09-wip/src/Exception/MethodNotAllowed.php b/implementation/09/src/Exception/MethodNotAllowed.php similarity index 100% rename from implementation/09-wip/src/Exception/MethodNotAllowed.php rename to implementation/09/src/Exception/MethodNotAllowed.php diff --git a/implementation/09-wip/src/Exception/NotFound.php b/implementation/09/src/Exception/NotFound.php similarity index 100% rename from implementation/09-wip/src/Exception/NotFound.php rename to implementation/09/src/Exception/NotFound.php diff --git a/implementation/09-wip/src/Service/Time/Clock.php b/implementation/09/src/Service/Time/Clock.php similarity index 100% rename from implementation/09-wip/src/Service/Time/Clock.php rename to implementation/09/src/Service/Time/Clock.php diff --git a/implementation/09-wip/src/Service/Time/SystemClock.php b/implementation/09/src/Service/Time/SystemClock.php similarity index 100% rename from implementation/09-wip/src/Service/Time/SystemClock.php rename to implementation/09/src/Service/Time/SystemClock.php From 572896685f9b56a8db206eaa3a418750d6cc3de5 Mon Sep 17 00:00:00 2001 From: lubiana Date: Sat, 21 May 2022 00:57:02 +0200 Subject: [PATCH 144/314] update implementation of chapter 9 --- implementation/09/composer.json | 4 +- implementation/09/composer.lock | 282 +++++++++++++++++- implementation/09/config/container.php | 26 +- implementation/09/src/Action/Hello.php | 9 +- implementation/09/src/Bootstrap.php | 17 +- implementation/09/src/Service/Time/Clock.php | 6 +- .../09/src/Service/Time/SystemClock.php | 9 +- 7 files changed, 329 insertions(+), 24 deletions(-) diff --git a/implementation/09/composer.json b/implementation/09/composer.json index 74e3551..335b122 100644 --- a/implementation/09/composer.json +++ b/implementation/09/composer.json @@ -16,7 +16,9 @@ "filp/whoops": "^2.14", "laminas/laminas-diactoros": "^2.11", "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0" + "psr/http-server-handler": "^1.0", + "psr/container": "^1.0", + "php-di/php-di": "^6.4" }, "require-dev": { "phpstan/phpstan": "^1.6", diff --git a/implementation/09/composer.lock b/implementation/09/composer.lock index a9f5b96..386eb40 100644 --- a/implementation/09/composer.lock +++ b/implementation/09/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5f281d245eeab688c1904bf024aeff4f", + "content-hash": "0b6833b8fa6869bd212824769648e667", "packages": [ { "name": "filp/whoops", @@ -176,6 +176,65 @@ ], "time": "2022-05-17T10:57:52+00:00" }, + { + "name": "laravel/serializable-closure", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "pestphp/pest": "^1.18", + "phpstan/phpstan": "^0.12.98", + "symfony/var-dumper": "^5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2022-05-16T17:09:47+00:00" + }, { "name": "nikic/fast-route", "version": "v1.3.0", @@ -226,6 +285,227 @@ }, "time": "2018-02-13T20:26:39+00:00" }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.4.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "shasum": "" + }, + "require": { + "laravel/serializable-closure": "^1.0", + "php": ">=7.4.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.10", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.11.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^9.5" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.4.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2022-04-09T16:46:38+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, { "name": "psr/http-factory", "version": "1.0.1", diff --git a/implementation/09/config/container.php b/implementation/09/config/container.php index 87eb322..ceb20dc 100644 --- a/implementation/09/config/container.php +++ b/implementation/09/config/container.php @@ -1,9 +1,23 @@ fn () => new \Lubian\NoFramework\Action\Hello($response, $clock), - \Lubian\NoFramework\Action\Other::class => fn () => new \Lubian\NoFramework\Action\Other($response), -]; +use function FastRoute\simpleDispatcher; + +$builder = new ContainerBuilder; + +$builder->addDefinitions([ + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + ResponseInterface::class => fn () => new Response, + Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + Clock::class => fn () => new SystemClock, +]); + +return $builder->build(); diff --git a/implementation/09/src/Action/Hello.php b/implementation/09/src/Action/Hello.php index 6b4d24a..ec2e00c 100644 --- a/implementation/09/src/Action/Hello.php +++ b/implementation/09/src/Action/Hello.php @@ -9,8 +9,10 @@ use Psr\Http\Server\RequestHandlerInterface; final class Hello implements RequestHandlerInterface { - public function __construct(private readonly ResponseInterface $response, private readonly Clock $clock) - { + public function __construct( + private readonly ResponseInterface $response, + private readonly Clock $clock + ) { } public function handle(ServerRequestInterface $request): ResponseInterface @@ -18,7 +20,8 @@ final class Hello implements RequestHandlerInterface $name = $request->getAttribute('name', 'Stranger'); $body = $this->response->getBody(); - $time = $this->clock->now()->format('H:i:s'); + $time = $this->clock->now() + ->format('H:i:s'); $body->write('Hello ' . $name . '!
'); $body->write('The Time is: ' . $time); diff --git a/implementation/09/src/Bootstrap.php b/implementation/09/src/Bootstrap.php index 98a3952..023c8c0 100644 --- a/implementation/09/src/Bootstrap.php +++ b/implementation/09/src/Bootstrap.php @@ -4,10 +4,11 @@ namespace Lubian\NoFramework; use FastRoute\Dispatcher; use Laminas\Diactoros\Response; -use Laminas\Diactoros\ServerRequestFactory; use Lubian\NoFramework\Exception\InternalServerError; use Lubian\NoFramework\Exception\MethodNotAllowed; use Lubian\NoFramework\Exception\NotFound; +use Psr\Container\ContainerInterface; +use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use Throwable; use Whoops\Handler\PrettyPageHandler; @@ -16,7 +17,6 @@ use Whoops\Run; use function assert; use function error_log; use function error_reporting; -use function FastRoute\simpleDispatcher; use function getenv; use function header; use function sprintf; @@ -43,12 +43,15 @@ if ($environment === 'dev') { $whoops->register(); -$request = ServerRequestFactory::fromGlobals(); -$response = new Response; +$container = require __DIR__ . '/../config/container.php'; +assert($container instanceof ContainerInterface); +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$dispatcher = $container->get(Dispatcher::class); +assert($dispatcher instanceof Dispatcher); -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = simpleDispatcher($routeDefinitionCallback); $routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); @@ -56,7 +59,7 @@ try { switch ($routeInfo[0]) { case Dispatcher::FOUND: $className = $routeInfo[1]; - $handler = new $className($response); + $handler = $container->get($className); assert($handler instanceof RequestHandlerInterface); foreach ($routeInfo[2] as $attributeName => $attributeValue) { $request = $request->withAttribute($attributeName, $attributeValue); diff --git a/implementation/09/src/Service/Time/Clock.php b/implementation/09/src/Service/Time/Clock.php index 50897dc..204ccb4 100644 --- a/implementation/09/src/Service/Time/Clock.php +++ b/implementation/09/src/Service/Time/Clock.php @@ -2,7 +2,9 @@ namespace Lubian\NoFramework\Service\Time; +use DateTimeImmutable; + interface Clock { - public function now(): \DateTimeImmutable; -} \ No newline at end of file + public function now(): DateTimeImmutable; +} diff --git a/implementation/09/src/Service/Time/SystemClock.php b/implementation/09/src/Service/Time/SystemClock.php index f0d0d0a..705d81f 100644 --- a/implementation/09/src/Service/Time/SystemClock.php +++ b/implementation/09/src/Service/Time/SystemClock.php @@ -2,11 +2,12 @@ namespace Lubian\NoFramework\Service\Time; +use DateTimeImmutable; + final class SystemClock implements Clock { - public function now(): \DateTimeImmutable + public function now(): DateTimeImmutable { - return new \DateTimeImmutable(); + return new DateTimeImmutable; } - -} \ No newline at end of file +} From 2fbbd082f22f961f21153dbbe28f0872fd800f90 Mon Sep 17 00:00:00 2001 From: lubiana Date: Sat, 21 May 2022 00:58:26 +0200 Subject: [PATCH 145/314] remove implementation from app directory --- app/composer.json | 50 - app/composer.lock | 1466 --------------------- app/config/container.php | 23 - app/config/routes.php | 10 - app/ecs.php | 89 -- app/phpstan-baseline.neon | 6 - app/phpstan.neon | 8 - app/public/index.php | 3 - app/rector.php | 12 - app/src/Action/Hello.php | 32 - app/src/Action/Other.php | 24 - app/src/Bootstrap.php | 104 -- app/src/Exception/InternalServerError.php | 9 - app/src/Exception/MethodNotAllowed.php | 9 - app/src/Exception/NotFound.php | 9 - app/src/Service/Time/Clock.php | 10 - app/src/Service/Time/SystemClock.php | 13 - 17 files changed, 1877 deletions(-) delete mode 100644 app/composer.json delete mode 100644 app/composer.lock delete mode 100644 app/config/container.php delete mode 100644 app/config/routes.php delete mode 100644 app/ecs.php delete mode 100644 app/phpstan-baseline.neon delete mode 100644 app/phpstan.neon delete mode 100644 app/public/index.php delete mode 100644 app/rector.php delete mode 100644 app/src/Action/Hello.php delete mode 100644 app/src/Action/Other.php delete mode 100644 app/src/Bootstrap.php delete mode 100644 app/src/Exception/InternalServerError.php delete mode 100644 app/src/Exception/MethodNotAllowed.php delete mode 100644 app/src/Exception/NotFound.php delete mode 100644 app/src/Service/Time/Clock.php delete mode 100644 app/src/Service/Time/SystemClock.php diff --git a/app/composer.json b/app/composer.json deleted file mode 100644 index 335b122..0000000 --- a/app/composer.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "lubian/no-framework", - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "example", - "email": "test@example.com" - } - ], - "require": { - "php": ">=8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.11", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "psr/container": "^1.0", - "php-di/php-di": "^6.4" - }, - "require-dev": { - "phpstan/phpstan": "^1.6", - "symfony/var-dumper": "^6.0", - "slevomat/coding-standard": "^7.2", - "symplify/easy-coding-standard": "^10.2", - "rector/rector": "^0.12.23", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.2", - "thecodingmachine/phpstan-strict-rules": "^1.0" - }, - "config": { - "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true, - "phpstan/extension-installer": true - } - }, - "scripts": { - "serve": [ - "Composer\\Config::disableProcessTimeout", - "php -S 0.0.0.0:1235 -t public" - ], - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/ecs", - "fix": "./vendor/bin/ecs --fix", - "rector": "./vendor/bin/rector process" - } -} diff --git a/app/composer.lock b/app/composer.lock deleted file mode 100644 index 386eb40..0000000 --- a/app/composer.lock +++ /dev/null @@ -1,1466 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "0b6833b8fa6869bd212824769648e667", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.11.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", - "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2022-05-17T10:57:52+00:00" - }, - { - "name": "laravel/serializable-closure", - "version": "v1.2.0", - "source": { - "type": "git", - "url": "https://github.com/laravel/serializable-closure.git", - "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", - "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", - "shasum": "" - }, - "require": { - "php": "^7.3|^8.0" - }, - "require-dev": { - "pestphp/pest": "^1.18", - "phpstan/phpstan": "^0.12.98", - "symfony/var-dumper": "^5.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Laravel\\SerializableClosure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - }, - { - "name": "Nuno Maduro", - "email": "nuno@laravel.com" - } - ], - "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", - "keywords": [ - "closure", - "laravel", - "serializable" - ], - "support": { - "issues": "https://github.com/laravel/serializable-closure/issues", - "source": "https://github.com/laravel/serializable-closure" - }, - "time": "2022-05-16T17:09:47+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.4.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/ae0f1b3b03d8b29dff81747063cbfd6276246cc4", - "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4", - "shasum": "" - }, - "require": { - "laravel/serializable-closure": "^1.0", - "php": ">=7.4.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.10", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.11.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^9.5" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.4.0" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2022-04-09T16:46:38+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "1.5.1", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "981cc368a216c988e862a75e526b6076987d1b50" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", - "reference": "981cc368a216c988e862a75e526b6076987d1b50", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.5", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5", - "symfony/process": "^5.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" - }, - "time": "2022-05-05T11:32:40+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.6.8", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", - "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.6.8" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-05-10T06:54:21+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.2.3", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", - "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.6.3" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" - }, - "time": "2022-05-04T15:20:40+00:00" - }, - { - "name": "rector/rector", - "version": "0.12.23", - "source": { - "type": "git", - "url": "https://github.com/rectorphp/rector.git", - "reference": "690b31768b322db886b35845f8452025eba2cacb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", - "reference": "690b31768b322db886b35845f8452025eba2cacb", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0", - "phpstan/phpstan": "^1.6" - }, - "conflict": { - "phpstan/phpdoc-parser": "<1.2", - "rector/rector-cakephp": "*", - "rector/rector-doctrine": "*", - "rector/rector-laravel": "*", - "rector/rector-nette": "*", - "rector/rector-phpoffice": "*", - "rector/rector-phpunit": "*", - "rector/rector-prefixed": "*", - "rector/rector-symfony": "*" - }, - "bin": [ - "bin/rector" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "0.12-dev" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Instant Upgrade and Automated Refactoring of any PHP code", - "support": { - "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/0.12.23" - }, - "funding": [ - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2022-05-01T15:50:16+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "7.2.0", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", - "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.2 || ^8.0", - "phpstan/phpdoc-parser": "^1.5.1", - "squizlabs/php_codesniffer": "^3.6.2" - }, - "require-dev": { - "phing/phing": "2.17.3", - "php-parallel-lint/php-parallel-lint": "1.3.2", - "phpstan/phpstan": "1.4.10|1.6.7", - "phpstan/phpstan-deprecation-rules": "1.0.0", - "phpstan/phpstan-phpunit": "1.0.0|1.1.1", - "phpstan/phpstan-strict-rules": "1.2.3", - "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "7.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2022-05-06T10:58:42+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.8", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", - "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-04-26T13:22:23+00:00" - }, - { - "name": "symplify/easy-coding-standard", - "version": "10.2.6", - "source": { - "type": "git", - "url": "https://github.com/symplify/easy-coding-standard.git", - "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", - "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "conflict": { - "friendsofphp/php-cs-fixer": "<3.0", - "squizlabs/php_codesniffer": "<3.6" - }, - "bin": [ - "bin/ecs" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "9.5-dev" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Prefixed scoped version of ECS package", - "support": { - "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" - }, - "funding": [ - { - "url": "https://www.paypal.me/rectorphp", - "type": "custom" - }, - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2022-05-17T07:11:50+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.3.0" -} diff --git a/app/config/container.php b/app/config/container.php deleted file mode 100644 index ceb20dc..0000000 --- a/app/config/container.php +++ /dev/null @@ -1,23 +0,0 @@ -addDefinitions([ - ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), - ResponseInterface::class => fn () => new Response, - Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), - Clock::class => fn () => new SystemClock, -]); - -return $builder->build(); diff --git a/app/config/routes.php b/app/config/routes.php deleted file mode 100644 index c9b632d..0000000 --- a/app/config/routes.php +++ /dev/null @@ -1,10 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/other', Other::class); -}; diff --git a/app/ecs.php b/app/ecs.php deleted file mode 100644 index 6742326..0000000 --- a/app/ecs.php +++ /dev/null @@ -1,89 +0,0 @@ -parallel(); - $config->paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); - $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); - - $config->sets([ - SetList::PSR_12, - SetList::STRICT, - SetList::ARRAY, - SetList::SPACES, - SetList::DOCBLOCK, - SetList::CLEAN_CODE, - SetList::COMMON, - SetList::COMMENTS, - SetList::NAMESPACES, - SetList::SYMPLIFY, - SetList::CONTROL_STRUCTURES, - ]); - - // force visibility declaration on class constants - $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ - 'fixable' => true, - ]); - - // sort all use statements - $config->rules([ - AlphabeticallySortedUsesSniff::class, - DisallowGroupUseSniff::class, - MultipleUsesPerLineSniff::class, - NamespaceSpacingSniff::class, - ]); - - // import all namespaces, and event php core functions and classes - $config->ruleWithConfiguration( - ReferenceUsedNamesOnlySniff::class, - [ - 'allowFallbackGlobalConstants' => false, - 'allowFallbackGlobalFunctions' => false, - 'allowFullyQualifiedGlobalClasses' => false, - 'allowFullyQualifiedGlobalConstants' => false, - 'allowFullyQualifiedGlobalFunctions' => false, - 'allowFullyQualifiedNameForCollidingClasses' => true, - 'allowFullyQualifiedNameForCollidingConstants' => true, - 'allowFullyQualifiedNameForCollidingFunctions' => true, - 'searchAnnotations' => true, - ] - ); - - // define newlines between use statements - $config->ruleWithConfiguration(UseSpacingSniff::class, [ - 'linesCountBeforeFirstUse' => 1, - 'linesCountBetweenUseTypes' => 1, - 'linesCountAfterLastUse' => 1, - ]); - - // strict types declaration should be on same line as opening tag - $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ - 'declareOnFirstLine' => true, - 'spacesCountAroundEqualsSign' => 0, - ]); - - // disallow ?Foo typehint in favor of Foo|null - $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ - 'withSpaces' => 'no', - 'shortNullable' => 'no', - 'nullPosition' => 'last', - ]); - - // Remove useless parentheses in new statements - $config->rule(NewWithoutParenthesesSniff::class); -}; diff --git a/app/phpstan-baseline.neon b/app/phpstan-baseline.neon deleted file mode 100644 index 38383b9..0000000 --- a/app/phpstan-baseline.neon +++ /dev/null @@ -1,6 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: src/Bootstrap.php diff --git a/app/phpstan.neon b/app/phpstan.neon deleted file mode 100644 index 2eac45a..0000000 --- a/app/phpstan.neon +++ /dev/null @@ -1,8 +0,0 @@ -includes: - - phpstan-baseline.neon - -parameters: - level: max - paths: - - src - - config \ No newline at end of file diff --git a/app/public/index.php b/app/public/index.php deleted file mode 100644 index 970d132..0000000 --- a/app/public/index.php +++ /dev/null @@ -1,3 +0,0 @@ -paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); - - $rectorConfig->importNames(); - - $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); -}; diff --git a/app/src/Action/Hello.php b/app/src/Action/Hello.php deleted file mode 100644 index ec2e00c..0000000 --- a/app/src/Action/Hello.php +++ /dev/null @@ -1,32 +0,0 @@ -getAttribute('name', 'Stranger'); - $body = $this->response->getBody(); - - $time = $this->clock->now() - ->format('H:i:s'); - - $body->write('Hello ' . $name . '!
'); - $body->write('The Time is: ' . $time); - - return $this->response->withBody($body) - ->withStatus(200); - } -} diff --git a/app/src/Action/Other.php b/app/src/Action/Other.php deleted file mode 100644 index c42c74b..0000000 --- a/app/src/Action/Other.php +++ /dev/null @@ -1,24 +0,0 @@ -response->getBody(); - - $body->write('This works too!'); - - return $this->response->withBody($body) - ->withStatus(200); - } -} diff --git a/app/src/Bootstrap.php b/app/src/Bootstrap.php deleted file mode 100644 index 023c8c0..0000000 --- a/app/src/Bootstrap.php +++ /dev/null @@ -1,104 +0,0 @@ -pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $t) { - error_log('ERROR: ' . $t->getMessage(), $t->getCode()); - echo 'Oooopsie'; - }); -} - -$whoops->register(); - -$container = require __DIR__ . '/../config/container.php'; -assert($container instanceof ContainerInterface); - -$request = $container->get(ServerRequestInterface::class); -assert($request instanceof ServerRequestInterface); - -$dispatcher = $container->get(Dispatcher::class); -assert($dispatcher instanceof Dispatcher); - - -$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); - -try { - switch ($routeInfo[0]) { - case Dispatcher::FOUND: - $className = $routeInfo[1]; - $handler = $container->get($className); - assert($handler instanceof RequestHandlerInterface); - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - break; - case Dispatcher::METHOD_NOT_ALLOWED: - throw new MethodNotAllowed; - case Dispatcher::NOT_FOUND: - default: - throw new NotFound; - } -} catch (MethodNotAllowed) { - $response = (new Response)->withStatus(405); - $response->getBody() - ->write('Method not Allowed'); -} catch (NotFound) { - $response = (new Response)->withStatus(404); - $response->getBody() - ->write('Not Found'); -} catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); -} - -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); diff --git a/app/src/Exception/InternalServerError.php b/app/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/app/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ - Date: Sat, 21 May 2022 01:19:14 +0200 Subject: [PATCH 146/314] fix some typos --- 03-error-handler.md | 4 ++-- 04-development-helpers.md | 24 ++++++++++++------------ 05-http.md | 12 ++++++------ 06-router.md | 8 ++++---- 07-dispatching-to-a-class.md | 10 +++++----- 08-inversion-of-control.md | 2 +- 09-dependency-injector.md | 22 +++++++++++----------- 7 files changed, 41 insertions(+), 41 deletions(-) diff --git a/03-error-handler.md b/03-error-handler.md index d1d7c06..099e34b 100644 --- a/03-error-handler.md +++ b/03-error-handler.md @@ -23,7 +23,7 @@ like this: }, ``` -Now run `composer update` in your console and it will be installed. +Now run `composer update` in your console, and it will be installed. Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, i that case composer automatically installs the package and updates your composer.json-file. @@ -33,7 +33,7 @@ ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer alread only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. **Important:** Never show any errors in your production environment. A stack trace or even just a simple error message -can help someone to gain access to your system. Always show a user friendly error page instead and send an email to +can help someone to gain access to your system. Always show a user-friendly error page instead and send an email to yourself, write to a log or something similar. So only you can see the errors in the production environment. For development that does not make sense though -- you want a nice error page. The solution is to have an environment diff --git a/04-development-helpers.md b/04-development-helpers.md index b9fcd50..595bb3d 100644 --- a/04-development-helpers.md +++ b/04-development-helpers.md @@ -4,7 +4,7 @@ I have added some more helpers to my composer.json that help me with development. As these are scripts and programms used only for development they should not be used in a production environment. Composer has a specific sections in its -file called "dev-dependencies", everything that is required in this section does not get installen in production. +file called "dev-dependencies", everything that is required in this section does not get installed in production. Let's install our dev-helpers and i will explain them one by one: `composer require --dev phpstan/phpstan symfony/var-dumper slevomat/coding-standard symplify/easy-coding-standard rector/rector` @@ -15,7 +15,7 @@ Phpstan is a great little tool, that tries to understand your code and checks if create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements that are not (or not always) available, if-statements that always are true and a lot of other stuff. -A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. +A very simple example would be a small functions that takes a DateTime-Object and prints it in a human-readable format. ```php /** @@ -45,9 +45,9 @@ Line Bootstrap.php ``` The second error is something that "declare strict-types" already catches for us, but the first error is something that -we usually would not discover easily without speccially looking for this errortype. +we usually would not discover easily without specially looking for this error-type. -We can add a simple configfile called `phpstan.neon` to our project so that we do not have to specify the errorlevel and +We can add a simple config file called `phpstan.neon` to our project so that we do not have to specify the error level and path everytime we want to check our code for errors: ```yaml @@ -59,7 +59,7 @@ parameters: now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us -some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. +some silly things, therefore we want to add install some packages that enforce rules that are a little stricter. ```shell composer require --dev phpstan/extension-installer @@ -67,7 +67,7 @@ composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-str ``` During the first install you need to allow the extension installer to actually install the extension. The second command -installs some more strict rulesets and activates them in phpstan. +installs some more strict rules and activates them in phpstan. If we now rerun phpstan it already tells us about some errors we have made: @@ -84,9 +84,9 @@ Line Bootstrap.php ``` The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove -that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific +that we should be able to fix that. The first error is something we could fix, but I don't want to focus on that specific problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working -on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages +on an old legacy codebase and wanted to add static analysis to it but can't because we would get 1 Million error messages everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we are adding to our code. @@ -128,7 +128,7 @@ which basically does the same has in my experience some more Rules available tha But we are going to use neither of those tools directly and instead choose the [Easy Coding Standard](https://github.com/symplify/easy-coding-standard) which allows us to combine rules from both mentioned tools, and also claims to run much faster. You could check out the documentation and decide on your own coding standard. Or use the one provided by me, which is base on PSR-12 but adds -some highly opiniated options. First create a file 'ecs.php' and either add your own configuration or copy the my +some highly opiniated options. First create a file 'ecs.php' and either add your own configuration or copy the prepared one: ```php @@ -288,8 +288,8 @@ with lots of parameters by hand all the time, so I added a few lines to my `comp }, ``` -that way I can just type "composer" followed by the command name in the root of my project. if i want to start the -php devserver I can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the +that way I can just type "composer" followed by the command name in the root of my project. if I want to start the +php dev server I can just type "composer serve" and don't have to type in the hostname, port and target directory all the time. You could also configure PhpStorm to automatically run these commands in the background and highlight the violations @@ -297,7 +297,7 @@ directly in the file you are currently editing. I personally am not a fan of thi flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. My workflow is to just write my code the way I currently feel and that execute the phpstan and the fix scripts before -commiting and pushing the code. There is a [highly opiniated blogpost](https://tomasvotruba.com/blog/2019/06/24/do-you-use-php-codesniffer-and-php-cs-fixer-phpstorm-plugin-you-are-slow-and-expensive/) +committing and pushing the code. There is a [highly opiniated blogpost](https://tomasvotruba.com/blog/2019/06/24/do-you-use-php-codesniffer-and-php-cs-fixer-phpstorm-plugin-you-are-slow-and-expensive/) discussing that topic further. That you can read. But in the end it boils down to what you are most comfortable with. [<< previous](03-error-handler.md) | [next >>](05-http.md) \ No newline at end of file diff --git a/05-http.md b/05-http.md index 61077a6..c7a3f05 100644 --- a/05-http.md +++ b/05-http.md @@ -19,7 +19,7 @@ the laminas/laminas-diactoros package as i am an old time fan of the laminas (pr Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a [lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. -Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use +Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore, I will not use that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). @@ -38,7 +38,7 @@ $response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. -In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the +In order to actually add content to the response you have to access the body stream object of the Response and use the write()-Method on that object. @@ -66,7 +66,7 @@ $response = $response->withStatus(200); $response = $response->withAddedHeader('Content-type', 'application/json'); ``` -If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been +If you have ever struggled with Mutation-problems in an DateTime-Object you might understand why the standard has been defined this way. But if you have been keeping attention you might argue that the following line should not work if the request object is @@ -80,7 +80,7 @@ The response-body implements a stream interface which is immutable for some reas [meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. -I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content +I, for one, am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content to a response object. ```php @@ -90,7 +90,7 @@ $response = $response->withBody($body); ``` Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our -output-logic a little bit more. Replace the line that echos the response-body with the following: +output-logic a little more. Replace the line that echos the response-body with the following: ```php foreach ($response->getHeaders() as $name => $values) { @@ -114,7 +114,7 @@ echo $response->getBody(); ``` This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a -webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. +browser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. diff --git a/06-router.md b/06-router.md index 10a0c93..66c08fa 100644 --- a/06-router.md +++ b/06-router.md @@ -63,10 +63,10 @@ dispatcher gets called and the appropriate part of the switch statement will be we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. -This setup might work for really small applications, but once you start adding a few routes your bootstrap file will +This setup might work for tiny applications, but once you start adding a few routes your bootstrap file will quickly get cluttered. So let's move them out into a separate file. -Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; +Create a new directory in you project root named 'config' and add a 'routes.php' file with the following content; ```php >](07-dispatching-to-a-class.md) diff --git a/07-dispatching-to-a-class.md b/07-dispatching-to-a-class.md index 830a30f..148f67b 100644 --- a/07-dispatching-to-a-class.md +++ b/07-dispatching-to-a-class.md @@ -33,8 +33,8 @@ final class Hello implements \Psr\Http\Server\RequestHandlerInterface ``` You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) -that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is -fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement +that has a 'handle'-Method with requires a Request object as its parameter and returns a Response-object. For now this is +fine, but we may have to change our approach later. In any way it is good to know about this interface as we will implement it in some other parts of our application as well. In order to use that Interface we have to require it with composer: `composer require psr/http-server-handler`. @@ -77,9 +77,9 @@ Now if you visit `http://localhost:1235/` everything should work. If not, go bac And of course don't forget to commit your changes. Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still -generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for +generated in the routing-matching section and not in special classes. Also, we have still left some cases to chance, for example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still -have our whoopsie error-handler but i like to be more explicit in my control flow. +have our whoopsie error-handler, but I like to be more explicit in my control flow. In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and @@ -99,7 +99,7 @@ final class NotFound extends Exception{} Use that example to create a MethodNotAllowedException.php and InternalServerErrorException.php as well. -After you have created those we update our Routercode to use the new Exceptions. +After you have created those we update our Router code to use the new Exceptions. ```php try { diff --git a/08-inversion-of-control.md b/08-inversion-of-control.md index 21f4f23..e9b1e90 100644 --- a/08-inversion-of-control.md +++ b/08-inversion-of-control.md @@ -7,7 +7,7 @@ want to switch to a more powerfull Http-Implementation later, or need to create we would need to edit every one of our request handlers to call a different constructor of the class. The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that -instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done +instead of giving the class the responsibility of creating the object it needs, you just ask for them. This is done with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is diff --git a/09-dependency-injector.md b/09-dependency-injector.md index 060a09f..01ff14f 100644 --- a/09-dependency-injector.md +++ b/09-dependency-injector.md @@ -4,13 +4,13 @@ In the last chapter we rewrote our Actions to require the response-objet as a constructor parameter, and provided it in the dispatcher section of our `Bootstrap.php`. As we only have one dependency this works really fine, but if we have -different classes with different dependencies our bootstrap file gets complicated quite quickly. Lets look at an example +different classes with different dependencies our bootstrap file gets complicated quite quickly. Let's look at an example to explain the problem and work on a solution. #### Adding a clock service Lets assume that we want to show the current time in our Hello action. We could easily just call use one of the many -ways to get the current time directly in the handle-method, but lets create a separate class and interface for that so +ways to get the current time directly in the handle-method, but let's create a separate class and interface for that so we can later configure and switch our implementation. We need a new 'Service\Time' namespace, so lets first create the folder in our 'src' directory 'src/Service/Time'. @@ -45,7 +45,7 @@ final class SystemClock implements Clock } ``` -Now we can require the Clockinterface as a depencency in our controller and use it to display the current time. +Now we can require the Clock interface as a dependency in our controller and use it to display the current time. ```php Too few arguments to function Lubian\NoFramework\Action\Hello::__construct(), 1 passed in /home/lubiana/PhpstormProjects/no-framework/app/src/Bootstrap.php on line 62 and exactly 2 expected Our current problem is, that we have two Actions defined, which both have different constructor requirements. That means, @@ -114,7 +114,7 @@ I mostly define an Interface or a fully qualified classname as an ID. That way I the Clock interface or an Action class and get an object of that class or an object implementing the given Interface. For the sake of this tutorial we will put a new file in our config folder that returns an anonymous class implementing -the containerinterface. +the container-interface. In this class we will configure all services required for our application and make them accessible via the get($id) method. @@ -164,13 +164,13 @@ return new class () implements \Psr\Container\ContainerInterface { }; ``` -Here I have declared a services array, that has a class- or interfacename as the keys, and the values are short +Here I have declared a services array, that has a class- or interface name as the keys, and the values are short closures that return an Object of the defined class or interface. The `has` method simply checks if the given id is defined in our services array, and the `get` method calls the closure defined in the array for the given id key and then returns the result of that closure. To use the container we need to update our Bootstrap.php. Firstly we need to get an instance of our container, and then -use that to create our Request-Object as well as the Dispatcher. So remove the manual instantion of those objects and +use that to create our Request-Object as well as the Dispatcher. So remove the manual instantiation of those objects and replace that with the following code: ```php @@ -201,7 +201,7 @@ assert($handler instanceof RequestHandlerInterface); If you now open the `/hello` route in your browser everything should work again! -#### Using Autowiring +#### Using Auto wiring If you take a critical look at the services array you might see that we need to manually define how our Hello- and Other-Action are getting constructed. This is quite repetitive, as we have already declared what objects to create @@ -215,9 +215,9 @@ functionality ourselves, or just try to use a library that takes care of that fo You can query the composer database to find all [libraries that implment the container interface](https://packagist.org/providers/psr/container-implementation). I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) -out of the box, and also solves the autowiring problem. +out of the box, and also solves the auto wiring problem. -Lets rewrite our `container.php` file to use the PHP-DI container and only define the Services the Container cannot +Let's rewrite our `container.php` file to use the PHP-DI container and only define the Services the Container cannot automatically build. ```php @@ -237,7 +237,7 @@ return $builder->build(); ``` As the PHP-DI container that is return by the `$builder->build()` method implements the same container interface as our -previously used ad-hoc container we won't need to update the our Bootstrap file and everything still works. +previously used ad-hoc container we won't need to update the Bootstrap file and everything still works. [<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file From 0b1fa54d49fecb8d34e3c3c1fdb217537d1b5d65 Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 23 May 2022 08:43:33 +0200 Subject: [PATCH 147/314] fix type in DI chapter --- 09-dependency-injector.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/09-dependency-injector.md b/09-dependency-injector.md index 01ff14f..14652ff 100644 --- a/09-dependency-injector.md +++ b/09-dependency-injector.md @@ -212,7 +212,7 @@ PHP provides us with the great Reflection Api that is capable of showing us, [wh given class requires](https://www.php.net/manual/de/reflectionclass.getconstructor.php]. We could implement that functionality ourselves, or just try to use a library that takes care of that for us. -You can query the composer database to find all [libraries that implment the container interface](https://packagist.org/providers/psr/container-implementation). +You can query the composer database to find all [libraries that implement the container interface](https://packagist.org/providers/psr/container-implementation). I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) out of the box, and also solves the auto wiring problem. @@ -223,7 +223,6 @@ automatically build. ```php addDefinitions([ From 6920ea390d020e6c68251078926a294af090a9a4 Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 23 May 2022 14:45:47 +0200 Subject: [PATCH 148/314] add solutions for chapter 9 and fix urltypos --- 09-dependency-injector.md | 10 ++++++---- implementation/09/composer.lock | 2 +- implementation/09/config/container.php | 16 ++++++++-------- implementation/09/src/Action/Hello.php | 7 ++----- implementation/09/src/Bootstrap.php | 1 - 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/09-dependency-injector.md b/09-dependency-injector.md index 14652ff..793fb06 100644 --- a/09-dependency-injector.md +++ b/09-dependency-injector.md @@ -16,7 +16,6 @@ we can later configure and switch our implementation. We need a new 'Service\Time' namespace, so lets first create the folder in our 'src' directory 'src/Service/Time'. There we place a Clock.php interface and a SystemClock.php implementation: - The Clock.php interface: ```php =8.1" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.2.0" } diff --git a/implementation/09/config/container.php b/implementation/09/config/container.php index ceb20dc..84e7386 100644 --- a/implementation/09/config/container.php +++ b/implementation/09/config/container.php @@ -1,7 +1,6 @@ addDefinitions([ - ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), - ResponseInterface::class => fn () => new Response, - Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), - Clock::class => fn () => new SystemClock, -]); +$builder->addDefinitions( + [ + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + ResponseInterface::class => fn () => new Response, + FastRoute\Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + Clock::class => fn () => new SystemClock, + ] +); return $builder->build(); diff --git a/implementation/09/src/Action/Hello.php b/implementation/09/src/Action/Hello.php index ec2e00c..012f1df 100644 --- a/implementation/09/src/Action/Hello.php +++ b/implementation/09/src/Action/Hello.php @@ -11,7 +11,7 @@ final class Hello implements RequestHandlerInterface { public function __construct( private readonly ResponseInterface $response, - private readonly Clock $clock + private readonly Clock $clock, ) { } @@ -20,11 +20,8 @@ final class Hello implements RequestHandlerInterface $name = $request->getAttribute('name', 'Stranger'); $body = $this->response->getBody(); - $time = $this->clock->now() - ->format('H:i:s'); - $body->write('Hello ' . $name . '!
'); - $body->write('The Time is: ' . $time); + $body->write('The time is: ' . $this->clock->now()->format('H:i:s')); return $this->response->withBody($body) ->withStatus(200); diff --git a/implementation/09/src/Bootstrap.php b/implementation/09/src/Bootstrap.php index 023c8c0..c5237b8 100644 --- a/implementation/09/src/Bootstrap.php +++ b/implementation/09/src/Bootstrap.php @@ -52,7 +52,6 @@ assert($request instanceof ServerRequestInterface); $dispatcher = $container->get(Dispatcher::class); assert($dispatcher instanceof Dispatcher); - $routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); try { From d880aeb9a65be20de02ea40ac9446e2aca3fbb3a Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 23 May 2022 22:06:21 +0200 Subject: [PATCH 149/314] add small typo and wording improvements to chapters 9 and 10, update name of time service --- 09-dependency-injector.md | 1 - 10-invoker.md | 43 +++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/09-dependency-injector.md b/09-dependency-injector.md index 793fb06..c35b99b 100644 --- a/09-dependency-injector.md +++ b/09-dependency-injector.md @@ -121,7 +121,6 @@ the container-interface. In this class we will configure all services required for our application and make them accessible via the get($id) method. -p Before we can implement the interface we need to install its definition with composer `composer require "psr/container:^1.0"`. now we can create a file with a Class that implements that interface. diff --git a/10-invoker.md b/10-invoker.md index 0ed6b59..4fa49aa 100644 --- a/10-invoker.md +++ b/10-invoker.md @@ -2,13 +2,13 @@ ### Invoker -Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the +Currently, all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long -with some services and not the whole Requestobject with all its various properties. +with some services and not the whole Request object with all its various properties. -If we take our Hello action for example we only need a response object, the time service and the 'name' information from -the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named -the class hello and it would be redundant to also call the the method hello. So an updated version of that class could +If we take our Hello action for example we only need a response object, the clock service and the 'name' information from +the request-uri. And as that class only provides one simple method we could easily make that invokable as we already named +the class hello, and it would be redundant to also call the method hello. So an updated version of that class could look like this: ```php @@ -16,17 +16,16 @@ final class Hello { public function __invoke( ResponseInterface $response, - Now $now, - string $name = 'Stranger', + Clock $clock, + string $name = 'Stranger' ): ResponseInterface { - $body = $this->response->getBody(); - $nowString = $now->get()->format('H:i:s'); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowString); - return $response - ->withBody($body) + $body = $response->getBody(); + + $body->write('Hello ' . $name . '!
'); + $body->write('The time is: ' . $clock->now()->format('H:i:s')); + + return $response->withBody($body) ->withStatus(200); } } @@ -34,17 +33,17 @@ final class Hello It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the -rootpath of our application yet. +root path of our application yet. ```php $r->addRoute('GET', '/hello[/{name}]', Hello::class); -$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); +$r->addRoute('GET', '/other-route', [Other::class, 'handle']); $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); ``` -In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the -route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that -class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what +In order to support this crazy route definitions we would need to write a lot of code for actually calling the result of +the route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of +that class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple callable we would need to manually use reflection as well to resolve all the arguments. @@ -68,13 +67,13 @@ $response = $container->call($handler, $args); Try to open [localhost:1235/](http://localhost:1235/) in your browser and check if you are getting redirected to '/hello'. But by now you should know that I do not like to depend on specific implementations and the call method is not defined in -the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container +the psr/container interface. Therefore, we would not be able to use that if we are ever switching to the symfony container or any other implementation. Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific -container implementation so we could use it with any container that implements the ContainerInterface. And best of all +container implementation, so we could use it with any container that implements the ContainerInterface. And best of all the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that -we could implement if we ever want to write our own implementation or we could write an adapter that uses a different +we could implement if we ever want to write our own implementation, or we could write an adapter that uses a different class that solves the same problem. But for now we are using the solution provided by PHP-DI. From e91a1668165e89a74dd0c6c2031491d50462b8d3 Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 23 May 2022 22:08:50 +0200 Subject: [PATCH 150/314] add solutions for chapter 10 --- implementation/10/composer.json | 50 + implementation/10/composer.lock | 1466 +++++++++++++++++ implementation/10/config/container.php | 23 + implementation/10/config/routes.php | 12 + implementation/10/ecs.php | 89 + implementation/10/phpstan-baseline.neon | 6 + implementation/10/phpstan.neon | 8 + implementation/10/public/favicon.ico | Bin 0 -> 15086 bytes implementation/10/public/index.php | 3 + implementation/10/rector.php | 12 + implementation/10/src/Action/Hello.php | 24 + implementation/10/src/Action/Other.php | 24 + implementation/10/src/Bootstrap.php | 105 ++ .../10/src/Exception/InternalServerError.php | 9 + .../10/src/Exception/MethodNotAllowed.php | 9 + implementation/10/src/Exception/NotFound.php | 9 + implementation/10/src/Service/Time/Clock.php | 10 + .../10/src/Service/Time/SystemClock.php | 13 + 18 files changed, 1872 insertions(+) create mode 100644 implementation/10/composer.json create mode 100644 implementation/10/composer.lock create mode 100644 implementation/10/config/container.php create mode 100644 implementation/10/config/routes.php create mode 100644 implementation/10/ecs.php create mode 100644 implementation/10/phpstan-baseline.neon create mode 100644 implementation/10/phpstan.neon create mode 100644 implementation/10/public/favicon.ico create mode 100644 implementation/10/public/index.php create mode 100644 implementation/10/rector.php create mode 100644 implementation/10/src/Action/Hello.php create mode 100644 implementation/10/src/Action/Other.php create mode 100644 implementation/10/src/Bootstrap.php create mode 100644 implementation/10/src/Exception/InternalServerError.php create mode 100644 implementation/10/src/Exception/MethodNotAllowed.php create mode 100644 implementation/10/src/Exception/NotFound.php create mode 100644 implementation/10/src/Service/Time/Clock.php create mode 100644 implementation/10/src/Service/Time/SystemClock.php diff --git a/implementation/10/composer.json b/implementation/10/composer.json new file mode 100644 index 0000000..335b122 --- /dev/null +++ b/implementation/10/composer.json @@ -0,0 +1,50 @@ +{ + "name": "lubian/no-framework", + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "example", + "email": "test@example.com" + } + ], + "require": { + "php": ">=8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.11", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "psr/container": "^1.0", + "php-di/php-di": "^6.4" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/implementation/10/composer.lock b/implementation/10/composer.lock new file mode 100644 index 0000000..d2d0350 --- /dev/null +++ b/implementation/10/composer.lock @@ -0,0 +1,1466 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "0b6833b8fa6869bd212824769648e667", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-05-17T10:57:52+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "pestphp/pest": "^1.18", + "phpstan/phpstan": "^0.12.98", + "symfony/var-dumper": "^5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2022-05-16T17:09:47+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.4.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "shasum": "" + }, + "require": { + "laravel/serializable-closure": "^1.0", + "php": ">=7.4.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.10", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.11.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^9.5" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.4.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2022-04-09T16:46:38+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/10/config/container.php b/implementation/10/config/container.php new file mode 100644 index 0000000..84e7386 --- /dev/null +++ b/implementation/10/config/container.php @@ -0,0 +1,23 @@ +addDefinitions( + [ + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + ResponseInterface::class => fn () => new Response, + FastRoute\Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + Clock::class => fn () => new SystemClock, + ] +); + +return $builder->build(); diff --git a/implementation/10/config/routes.php b/implementation/10/config/routes.php new file mode 100644 index 0000000..23ff0ef --- /dev/null +++ b/implementation/10/config/routes.php @@ -0,0 +1,12 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/other', [Other::class, 'handle']); + $r->addRoute('GET', '/', fn (RI $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/implementation/10/ecs.php b/implementation/10/ecs.php new file mode 100644 index 0000000..6742326 --- /dev/null +++ b/implementation/10/ecs.php @@ -0,0 +1,89 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/implementation/10/phpstan-baseline.neon b/implementation/10/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/implementation/10/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/implementation/10/phpstan.neon b/implementation/10/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/10/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/10/public/favicon.ico b/implementation/10/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/10/public/index.php b/implementation/10/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/10/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/implementation/10/src/Action/Hello.php b/implementation/10/src/Action/Hello.php new file mode 100644 index 0000000..379ea73 --- /dev/null +++ b/implementation/10/src/Action/Hello.php @@ -0,0 +1,24 @@ +getBody(); + + $body->write('Hello ' . $name . '!
'); + $body->write('The time is: ' . $clock->now()->format('H:i:s')); + + return $response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/10/src/Action/Other.php b/implementation/10/src/Action/Other.php new file mode 100644 index 0000000..c42c74b --- /dev/null +++ b/implementation/10/src/Action/Other.php @@ -0,0 +1,24 @@ +response->getBody(); + + $body->write('This works too!'); + + return $this->response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/10/src/Bootstrap.php b/implementation/10/src/Bootstrap.php new file mode 100644 index 0000000..26fd2ea --- /dev/null +++ b/implementation/10/src/Bootstrap.php @@ -0,0 +1,105 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +$container = require __DIR__ . '/../config/container.php'; +assert($container instanceof ContainerInterface); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$dispatcher = $container->get(Dispatcher::class); +assert($dispatcher instanceof Dispatcher); + +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $routeInfo[2]['request'] = $request; + assert($container instanceof InvokerInterface); + $response = $container->call($routeInfo[1], $routeInfo[2]); + assert($response instanceof ResponseInterface); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody() + ->write('Method not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody() + ->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/10/src/Exception/InternalServerError.php b/implementation/10/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/10/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ + Date: Mon, 30 May 2022 14:27:48 +0200 Subject: [PATCH 151/314] update chapter 10 solutions --- implementation/10/config/routes.php | 4 ++-- implementation/10/src/Action/Hello.php | 3 +-- implementation/10/src/Bootstrap.php | 10 ++++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/implementation/10/config/routes.php b/implementation/10/config/routes.php index 23ff0ef..b68712f 100644 --- a/implementation/10/config/routes.php +++ b/implementation/10/config/routes.php @@ -3,10 +3,10 @@ use FastRoute\RouteCollector; use Lubian\NoFramework\Action\Hello; use Lubian\NoFramework\Action\Other; -use Psr\Http\Message\ResponseInterface as RI; +use Psr\Http\Message\ResponseInterface; return function (RouteCollector $r) { $r->addRoute('GET', '/hello[/{name}]', Hello::class); $r->addRoute('GET', '/other', [Other::class, 'handle']); - $r->addRoute('GET', '/', fn (RI $r) => $r->withStatus(302)->withHeader('Location', '/hello')); + $r->addRoute('GET', '/', fn (ResponseInterface $r) => $r->withHeader('Location', '/hello') ->withStatus(302)); }; diff --git a/implementation/10/src/Action/Hello.php b/implementation/10/src/Action/Hello.php index 379ea73..dfb8df8 100644 --- a/implementation/10/src/Action/Hello.php +++ b/implementation/10/src/Action/Hello.php @@ -11,8 +11,7 @@ final class Hello ResponseInterface $response, Clock $clock, string $name = 'Stranger' - ): ResponseInterface - { + ): ResponseInterface { $body = $response->getBody(); $body->write('Hello ' . $name . '!
'); diff --git a/implementation/10/src/Bootstrap.php b/implementation/10/src/Bootstrap.php index 26fd2ea..8acaa81 100644 --- a/implementation/10/src/Bootstrap.php +++ b/implementation/10/src/Bootstrap.php @@ -11,7 +11,6 @@ use Lubian\NoFramework\Exception\NotFound; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Server\RequestHandlerInterface; use Throwable; use Whoops\Handler\PrettyPageHandler; use Whoops\Run; @@ -59,12 +58,15 @@ $routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->g try { switch ($routeInfo[0]) { case Dispatcher::FOUND: + $routeTarget = $routeInfo[1]; + $args = $routeInfo[2]; foreach ($routeInfo[2] as $attributeName => $attributeValue) { $request = $request->withAttribute($attributeName, $attributeValue); } - $routeInfo[2]['request'] = $request; - assert($container instanceof InvokerInterface); - $response = $container->call($routeInfo[1], $routeInfo[2]); + $args['request'] = $request; + $invoker = $container->get(InvokerInterface::class); + assert($invoker instanceof InvokerInterface); + $response = $invoker->call($routeTarget, $args); assert($response instanceof ResponseInterface); break; case Dispatcher::METHOD_NOT_ALLOWED: From 9b14e32639be24f5577288a6db962f32518b0252 Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 30 May 2022 20:39:42 +0200 Subject: [PATCH 152/314] update chapter 11 --- 11-templating.md | 85 +++--------------------------------------------- 1 file changed, 4 insertions(+), 81 deletions(-) diff --git a/11-templating.md b/11-templating.md index 7bfe1aa..dc2d765 100644 --- a/11-templating.md +++ b/11-templating.md @@ -50,7 +50,7 @@ namespace Lubian\NoFramework\Template; interface Renderer { /** @param array $data */ - public function render(string $template, array $data = []) : string; + public function render(string $template, array $data = []): string; } ``` @@ -146,91 +146,14 @@ we can then use it in the factory. In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: ``` -

Hello World

-Hello {{ name }} +

Hello {{name}}

+

The time is: {{time}}

``` Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` Navigate to the hello page in your browser to make sure everything works. -One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies -file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration -values. - -Lets create a 'Settings' class in our './src' Folder: - -```php -addDefinitions([ - Settings::class => fn () => require __DIR__ '/settings.php', - ResponseInterface::class => create(Response::class), - ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), - Renderer::class => fn (ME $me) => new Mustache($me), - MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), -]); - -return $builder->build(); -``` - - - -And as always, don't forget to commit your changes. - +Before you move on to the next chapter be sure to run our quality tools and commit your changes. [<< previous](10-invoker.md) | [next >>](12-configuration.md) From 350f8e18b9fd91f525e993a1581b99d37e062be9 Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 30 May 2022 20:40:45 +0200 Subject: [PATCH 153/314] add chapter 11 solutions --- implementation/11/composer.json | 51 + implementation/11/composer.lock | 1516 +++++++++++++++++ implementation/11/config/container.php | 36 + implementation/11/config/routes.php | 12 + implementation/11/ecs.php | 89 + implementation/11/phpstan-baseline.neon | 6 + implementation/11/phpstan.neon | 8 + implementation/11/public/favicon.ico | Bin 0 -> 15086 bytes implementation/11/public/index.php | 3 + implementation/11/rector.php | 12 + implementation/11/src/Action/Hello.php | 31 + implementation/11/src/Action/Other.php | 24 + implementation/11/src/Bootstrap.php | 107 ++ .../11/src/Exception/InternalServerError.php | 9 + .../11/src/Exception/MethodNotAllowed.php | 9 + implementation/11/src/Exception/NotFound.php | 9 + implementation/11/src/Service/Time/Clock.php | 10 + .../11/src/Service/Time/SystemClock.php | 13 + .../11/src/Template/MustacheRenderer.php | 17 + implementation/11/src/Template/Renderer.php | 11 + implementation/11/templates/hello.html | 2 + 21 files changed, 1975 insertions(+) create mode 100644 implementation/11/composer.json create mode 100644 implementation/11/composer.lock create mode 100644 implementation/11/config/container.php create mode 100644 implementation/11/config/routes.php create mode 100644 implementation/11/ecs.php create mode 100644 implementation/11/phpstan-baseline.neon create mode 100644 implementation/11/phpstan.neon create mode 100644 implementation/11/public/favicon.ico create mode 100644 implementation/11/public/index.php create mode 100644 implementation/11/rector.php create mode 100644 implementation/11/src/Action/Hello.php create mode 100644 implementation/11/src/Action/Other.php create mode 100644 implementation/11/src/Bootstrap.php create mode 100644 implementation/11/src/Exception/InternalServerError.php create mode 100644 implementation/11/src/Exception/MethodNotAllowed.php create mode 100644 implementation/11/src/Exception/NotFound.php create mode 100644 implementation/11/src/Service/Time/Clock.php create mode 100644 implementation/11/src/Service/Time/SystemClock.php create mode 100644 implementation/11/src/Template/MustacheRenderer.php create mode 100644 implementation/11/src/Template/Renderer.php create mode 100644 implementation/11/templates/hello.html diff --git a/implementation/11/composer.json b/implementation/11/composer.json new file mode 100644 index 0000000..c0fe924 --- /dev/null +++ b/implementation/11/composer.json @@ -0,0 +1,51 @@ +{ + "name": "lubian/no-framework", + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "example", + "email": "test@example.com" + } + ], + "require": { + "php": ">=8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.11", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "psr/container": "^1.0", + "php-di/php-di": "^6.4", + "mustache/mustache": "^2.14" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/implementation/11/composer.lock b/implementation/11/composer.lock new file mode 100644 index 0000000..c3de49e --- /dev/null +++ b/implementation/11/composer.lock @@ -0,0 +1,1516 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "ea5b586e05e6b1d75bded814662047b4", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-05-17T10:57:52+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "pestphp/pest": "^1.18", + "phpstan/phpstan": "^0.12.98", + "symfony/var-dumper": "^5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2022-05-16T17:09:47+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.4.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "shasum": "" + }, + "require": { + "laravel/serializable-closure": "^1.0", + "php": ">=7.4.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.10", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.11.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^9.5" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.4.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2022-04-09T16:46:38+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/11/config/container.php b/implementation/11/config/container.php new file mode 100644 index 0000000..de7b1d4 --- /dev/null +++ b/implementation/11/config/container.php @@ -0,0 +1,36 @@ +addDefinitions( + [ + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + ResponseInterface::class => fn () => new Response, + Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + Clock::class => fn () => new SystemClock, + Renderer::class => fn (MustacheRenderer $me) => $me, + Mustache_Loader_FilesystemLoader::class => fn () => new Mustache_Loader_FilesystemLoader( + __DIR__ . '/../templates', + [ + 'extension' => '.html', + ] + ), + Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $loader) => new Mustache_Engine([ + 'loader' => $loader, + ]), + ] +); + +return $builder->build(); diff --git a/implementation/11/config/routes.php b/implementation/11/config/routes.php new file mode 100644 index 0000000..b68712f --- /dev/null +++ b/implementation/11/config/routes.php @@ -0,0 +1,12 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/other', [Other::class, 'handle']); + $r->addRoute('GET', '/', fn (ResponseInterface $r) => $r->withHeader('Location', '/hello') ->withStatus(302)); +}; diff --git a/implementation/11/ecs.php b/implementation/11/ecs.php new file mode 100644 index 0000000..6742326 --- /dev/null +++ b/implementation/11/ecs.php @@ -0,0 +1,89 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/implementation/11/phpstan-baseline.neon b/implementation/11/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/implementation/11/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/implementation/11/phpstan.neon b/implementation/11/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/11/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/11/public/favicon.ico b/implementation/11/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/11/public/index.php b/implementation/11/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/11/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/implementation/11/src/Action/Hello.php b/implementation/11/src/Action/Hello.php new file mode 100644 index 0000000..98531eb --- /dev/null +++ b/implementation/11/src/Action/Hello.php @@ -0,0 +1,31 @@ + $name, + 'time' => $clock->now() + ->format('H:i:s'), + ]; + + $content = $renderer->render('hello', $data,); + + $body = $response->getBody(); + $body->write($content); + + return $response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/11/src/Action/Other.php b/implementation/11/src/Action/Other.php new file mode 100644 index 0000000..c42c74b --- /dev/null +++ b/implementation/11/src/Action/Other.php @@ -0,0 +1,24 @@ +response->getBody(); + + $body->write('This works too!'); + + return $this->response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/11/src/Bootstrap.php b/implementation/11/src/Bootstrap.php new file mode 100644 index 0000000..8acaa81 --- /dev/null +++ b/implementation/11/src/Bootstrap.php @@ -0,0 +1,107 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +$container = require __DIR__ . '/../config/container.php'; +assert($container instanceof ContainerInterface); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$dispatcher = $container->get(Dispatcher::class); +assert($dispatcher instanceof Dispatcher); + +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + $routeTarget = $routeInfo[1]; + $args = $routeInfo[2]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $args['request'] = $request; + $invoker = $container->get(InvokerInterface::class); + assert($invoker instanceof InvokerInterface); + $response = $invoker->call($routeTarget, $args); + assert($response instanceof ResponseInterface); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody() + ->write('Method not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody() + ->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/11/src/Exception/InternalServerError.php b/implementation/11/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/11/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +engine->render($template, $data); + } +} diff --git a/implementation/11/src/Template/Renderer.php b/implementation/11/src/Template/Renderer.php new file mode 100644 index 0000000..de84970 --- /dev/null +++ b/implementation/11/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data): string; +} diff --git a/implementation/11/templates/hello.html b/implementation/11/templates/hello.html new file mode 100644 index 0000000..ca12a59 --- /dev/null +++ b/implementation/11/templates/hello.html @@ -0,0 +1,2 @@ +

Hello {{name}}

+

The time is: {{time}}

\ No newline at end of file From f857fa475239729cb9068ba745a82cd955c34917 Mon Sep 17 00:00:00 2001 From: lubiana Date: Tue, 31 May 2022 17:48:26 +0200 Subject: [PATCH 154/314] simplify chapter 12 --- 12-configuration.md | 216 ++++++++++++-------------------------------- 1 file changed, 59 insertions(+), 157 deletions(-) diff --git a/12-configuration.md b/12-configuration.md index 4b60c19..bb3920d 100644 --- a/12-configuration.md +++ b/12-configuration.md @@ -4,197 +4,99 @@ In the last chapter we added some more definitions to our dependencies.php in that definitions we needed to pass quite a few configuration settings and filesystem strings to the constructors -of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. +of the classes. This might work for a small projects, but if we are growing we want to source that out to a more +explicit file that holds all the configuration values for our project. -As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. +As this is not a problem unique to our project there are already a some options available. Some projects use +[.env](https://github.com/vlucas/phpdotenv) files, others use +[.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is +[yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex +Readers for many configuration file formats that can be used, take a look at the +[laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. -As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. +As I am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am +quite happy that PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at +[this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic +before moving on. -Lets create a 'Settings' class in our './src' Folder: +For the purpose of this Tutorial I will use a simple ValueObject that has all our configuration values as properties. +create a `Configuration.php` class in the `./src` folder: ```php fn (Configuration $c) => simpleDispatcher(require $c->routesFile), + Mustache_Loader_FilesystemLoader::class => fn (Configuration $c) => new Mustache_Loader_FilesystemLoader( + $c->templateDir, + [ + 'extension' => $c->templateExtension, + ] + ), +``` + +Magically this is all we need to do, as the PHP-DI container knows that all constructor parameters of our configuration +class have default values and can create the needed object on its own. + +There is one small problem: If we want to change environment from `dev` to `prod` we would need to update the +configuration class in the src directory. This is something we don't want to do on every deployment. So lets add a file +in our `./config` directory called `settings.php` that returns a Configuration object. ```php fn () => require __DIR__ . '/settings.php', ``` -And write a simple implementation that uses our settings.php to provide our App with the Settingsobject: - +One small oversight to fix is in the registration of our error-handler in the bootstrap-file. There we read the +environment with the getenv-method. Lets change the line: ```php -filePath; - } -} +$environment = getenv('ENVIRONMENT') ?: 'dev'; ``` -If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files -and craft a settings object from them. - -As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another -factory for our Container because everyone should have a Friend :) - +to: ```php -environment; ``` -And a simple implementation that uses our new Settingsprovider to build the container: - -```php -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn () => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} -``` - -For this to work we need to change our dependencies.php file to just return the array of definitions: -And here we can instantly use the Settings object to create our template engine. - -```php - fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), -]; -``` - -Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: - -```php -... -error_reporting(E_ALL); - -$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); -$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); -... -``` - -Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. +Check if everything still works, run your code quality checks and commit the changes before moving on the next chapter. +You might notice that phpstan throws an error as there is a documented violation missing. You can either regenerate the +baseline, or simply remove that line from the `phpstan-baseline.neon` file. [<< previous](11-templating.md) | [next >>](13-refactoring.md) From ebbcc6e7f6fce22e2d8583c663c47ac56efb0bc0 Mon Sep 17 00:00:00 2001 From: lubiana Date: Tue, 31 May 2022 17:49:17 +0200 Subject: [PATCH 155/314] add chapter 12 solutions --- implementation/12/composer.json | 51 + implementation/12/composer.lock | 1516 +++++++++++++++++ implementation/12/config/container.php | 38 + implementation/12/config/routes.php | 12 + implementation/12/config/settings.php | 5 + implementation/12/ecs.php | 89 + implementation/12/phpstan-baseline.neon | 6 + implementation/12/phpstan.neon | 8 + implementation/12/public/favicon.ico | Bin 0 -> 15086 bytes implementation/12/public/index.php | 3 + implementation/12/rector.php | 12 + implementation/12/src/Action/Hello.php | 31 + implementation/12/src/Action/Other.php | 24 + implementation/12/src/Bootstrap.php | 109 ++ implementation/12/src/Configuration.php | 14 + .../12/src/Exception/InternalServerError.php | 9 + .../12/src/Exception/MethodNotAllowed.php | 9 + implementation/12/src/Exception/NotFound.php | 9 + implementation/12/src/Service/Time/Clock.php | 10 + .../12/src/Service/Time/SystemClock.php | 13 + .../12/src/Template/MustacheRenderer.php | 17 + implementation/12/src/Template/Renderer.php | 11 + implementation/12/templates/hello.html | 2 + 23 files changed, 1998 insertions(+) create mode 100644 implementation/12/composer.json create mode 100644 implementation/12/composer.lock create mode 100644 implementation/12/config/container.php create mode 100644 implementation/12/config/routes.php create mode 100644 implementation/12/config/settings.php create mode 100644 implementation/12/ecs.php create mode 100644 implementation/12/phpstan-baseline.neon create mode 100644 implementation/12/phpstan.neon create mode 100644 implementation/12/public/favicon.ico create mode 100644 implementation/12/public/index.php create mode 100644 implementation/12/rector.php create mode 100644 implementation/12/src/Action/Hello.php create mode 100644 implementation/12/src/Action/Other.php create mode 100644 implementation/12/src/Bootstrap.php create mode 100644 implementation/12/src/Configuration.php create mode 100644 implementation/12/src/Exception/InternalServerError.php create mode 100644 implementation/12/src/Exception/MethodNotAllowed.php create mode 100644 implementation/12/src/Exception/NotFound.php create mode 100644 implementation/12/src/Service/Time/Clock.php create mode 100644 implementation/12/src/Service/Time/SystemClock.php create mode 100644 implementation/12/src/Template/MustacheRenderer.php create mode 100644 implementation/12/src/Template/Renderer.php create mode 100644 implementation/12/templates/hello.html diff --git a/implementation/12/composer.json b/implementation/12/composer.json new file mode 100644 index 0000000..c0fe924 --- /dev/null +++ b/implementation/12/composer.json @@ -0,0 +1,51 @@ +{ + "name": "lubian/no-framework", + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "example", + "email": "test@example.com" + } + ], + "require": { + "php": ">=8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.11", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "psr/container": "^1.0", + "php-di/php-di": "^6.4", + "mustache/mustache": "^2.14" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/implementation/12/composer.lock b/implementation/12/composer.lock new file mode 100644 index 0000000..c3de49e --- /dev/null +++ b/implementation/12/composer.lock @@ -0,0 +1,1516 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "ea5b586e05e6b1d75bded814662047b4", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-05-17T10:57:52+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "pestphp/pest": "^1.18", + "phpstan/phpstan": "^0.12.98", + "symfony/var-dumper": "^5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2022-05-16T17:09:47+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.4.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "shasum": "" + }, + "require": { + "laravel/serializable-closure": "^1.0", + "php": ">=7.4.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.10", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.11.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^9.5" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.4.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2022-04-09T16:46:38+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/12/config/container.php b/implementation/12/config/container.php new file mode 100644 index 0000000..1f76fa2 --- /dev/null +++ b/implementation/12/config/container.php @@ -0,0 +1,38 @@ +addDefinitions( + [ + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + ResponseInterface::class => fn () => new Response, + Clock::class => fn () => new SystemClock, + Renderer::class => fn (MustacheRenderer $me) => $me, + Dispatcher::class => fn (Configuration $c) => simpleDispatcher(require $c->routesFile), + Mustache_Loader_FilesystemLoader::class => fn (Configuration $c) => new Mustache_Loader_FilesystemLoader( + $c->templateDir, + [ + 'extension' => $c->templateExtension, + ] + ), + Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $loader) => new Mustache_Engine([ + 'loader' => $loader, + ]), + Configuration::class => fn () => require __DIR__ . '/settings.php', + ] +); + +return $builder->build(); diff --git a/implementation/12/config/routes.php b/implementation/12/config/routes.php new file mode 100644 index 0000000..b68712f --- /dev/null +++ b/implementation/12/config/routes.php @@ -0,0 +1,12 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/other', [Other::class, 'handle']); + $r->addRoute('GET', '/', fn (ResponseInterface $r) => $r->withHeader('Location', '/hello') ->withStatus(302)); +}; diff --git a/implementation/12/config/settings.php b/implementation/12/config/settings.php new file mode 100644 index 0000000..1862e1f --- /dev/null +++ b/implementation/12/config/settings.php @@ -0,0 +1,5 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/implementation/12/phpstan-baseline.neon b/implementation/12/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/implementation/12/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/implementation/12/phpstan.neon b/implementation/12/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/12/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/12/public/favicon.ico b/implementation/12/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/12/public/index.php b/implementation/12/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/12/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/implementation/12/src/Action/Hello.php b/implementation/12/src/Action/Hello.php new file mode 100644 index 0000000..98531eb --- /dev/null +++ b/implementation/12/src/Action/Hello.php @@ -0,0 +1,31 @@ + $name, + 'time' => $clock->now() + ->format('H:i:s'), + ]; + + $content = $renderer->render('hello', $data,); + + $body = $response->getBody(); + $body->write($content); + + return $response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/12/src/Action/Other.php b/implementation/12/src/Action/Other.php new file mode 100644 index 0000000..c42c74b --- /dev/null +++ b/implementation/12/src/Action/Other.php @@ -0,0 +1,24 @@ +response->getBody(); + + $body->write('This works too!'); + + return $this->response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/12/src/Bootstrap.php b/implementation/12/src/Bootstrap.php new file mode 100644 index 0000000..152edf4 --- /dev/null +++ b/implementation/12/src/Bootstrap.php @@ -0,0 +1,109 @@ +environment; + +error_reporting(E_ALL); + +$whoops = new Run; + +if ($environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +$container = require __DIR__ . '/../config/container.php'; +assert($container instanceof ContainerInterface); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$dispatcher = $container->get(Dispatcher::class); +assert($dispatcher instanceof Dispatcher); + +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + $routeTarget = $routeInfo[1]; + $args = $routeInfo[2]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $args['request'] = $request; + $invoker = $container->get(InvokerInterface::class); + assert($invoker instanceof InvokerInterface); + $response = $invoker->call($routeTarget, $args); + assert($response instanceof ResponseInterface); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody() + ->write('Method not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody() + ->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/12/src/Configuration.php b/implementation/12/src/Configuration.php new file mode 100644 index 0000000..7483fe8 --- /dev/null +++ b/implementation/12/src/Configuration.php @@ -0,0 +1,14 @@ +engine->render($template, $data); + } +} diff --git a/implementation/12/src/Template/Renderer.php b/implementation/12/src/Template/Renderer.php new file mode 100644 index 0000000..de84970 --- /dev/null +++ b/implementation/12/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data): string; +} diff --git a/implementation/12/templates/hello.html b/implementation/12/templates/hello.html new file mode 100644 index 0000000..ca12a59 --- /dev/null +++ b/implementation/12/templates/hello.html @@ -0,0 +1,2 @@ +

Hello {{name}}

+

The time is: {{time}}

\ No newline at end of file From f5c444d4c74d54def04e710db7e656eb6c98004e Mon Sep 17 00:00:00 2001 From: lubiana Date: Tue, 31 May 2022 18:05:46 +0200 Subject: [PATCH 156/314] fix typos in chapters 15 to 17 --- 15-adding-content.md | 24 ++++++++++++------------ 16-data-repository.md | 24 ++++++++++++------------ 17-performance.md | 14 +++++++------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/15-adding-content.md b/15-adding-content.md index 0e85af0..753c395 100644 --- a/15-adding-content.md +++ b/15-adding-content.md @@ -2,7 +2,7 @@ ### Adding Content -By now we did not really display anything but some examples to in our application and it is now time to make our app +By now we did not really display anything but some examples to in our application, and it is now time to make our app display some content. For example we could our app be able to display the Markdown files used in this tutorial as nicely rendered HTML Pages that can be viewed in the browser instead of the editor you are using. @@ -15,7 +15,7 @@ can use whatever you like. After installing Parsedown lets write a Markdownparser interface and an implementation using parsedown. -We only need one function that receives a string of Markdown and returns the HTML represantion (as a string as well). +We only need one function that receives a string of Markdown and returns the HTML representation (as a string as well). @@ -30,8 +30,8 @@ interface MarkdownParser } ``` -By the namespace you will already have guessed that I called placed in interface in a file calles MarkdownParser.php in -the src/Template folder. Lets put our Parsedown implementation right next to it in a file called ParsedownParser.php +By the namespace you will already have guessed that I placed in interface in a file calles MarkdownParser.php in +the src/Template folder. Let's put our Parsedown implementation right next to it in a file called ParsedownParser.php ```php addRoute('GET', '/page', [Page::class, 'list']); $r->addRoute('GET', '/page/{page}', [Page::class, 'show']); ``` -Here is my Implementation. If have added a little regex replacement in the show method that replaces the links to the +Here is my Implementation. I have added a little regex replacement in the show method that replaces the links to the next and previous chapter so that it works with our routing configuration. `src/Action/Page.php` @@ -244,9 +244,9 @@ class Page You can now navigate your Browser to [localhost:1235/page][http://localhost:1235/page] and try out if everything works. Of course this code is far from looking good. We heavily rely on the pages being files in the filesystem, and the action -should never be aware of the filesystem in the first place, also we have a lot of string replacements and other repetetive -code in the file. And phpstan is gonna scream at us a lot, but if we rewrite the code to satisfy all the checks we would -add even more lines to that simple class, so lets move on to the next chapter where we move all the logic to seperate +should never be aware of the filesystem in the first place, also we have a lot of string replacements and other repetitive +code in the file. And phpstan is going to scream at us a lot, but if we rewrite the code to satisfy all the checks we would +add even more lines to that simple class, so lets move on to the next chapter where we move all the logic to separate classes following our holy SOLID principles :) diff --git a/16-data-repository.md b/16-data-repository.md index d9a3218..1677f0a 100644 --- a/16-data-repository.md +++ b/16-data-repository.md @@ -2,32 +2,32 @@ ## Data Repository -At the end of the last chapter I mentioned being unhappy with our Pages action, because there is to much stuff happening -there. We are firstly receiving some Arguments, then we are using those to query the filesytem for the given page, +At the end of the last chapter I mentioned being unhappy with our Pages action, because there is too much stuff happening +there. We are firstly receiving some Arguments, then we are using those to query the filesystem for the given page, loading the specific file from the filesystem, rendering the markdown, passing the markdown to the template renderer, adding the resulting html to the response and then returning the response. -In order to make our pageaction independent from the filesystem and move the code that is responsible for reading the +In order to make our page-action independent of the filesystem and move the code that is responsible for reading the files to a better place I want to introduce the [Repository Pattern](https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html). -I want to start by creating a class that represents the Data that is included in a page so that. For now I can spot +I want to start by creating a class that represents the Data that is included in a page so that. For now, I can spot three -distrinct attributes. +distinct attributes. -* the ID (or chapternumber) +* the ID (or chapter-number) * the title (or name) * the content -Currently all those properties are always available, but we might later be able to create new pages and store them, but +Currently, all those properties are always available, but we might later be able to create new pages and store them, but at that point in time we are not yet aware of the new available ID, so we should leave that property nullable. This allows -us to create an object without an id and let the code that actually saves the object to a persistant store define a +us to create an object without an id and let the code that actually saves the object to a persistent store define a valid id on saving. -Lets create an new Namespace called `Model` and put a `MarkdownPage.php` class in there: +Let's create an new Namespace called `Model` and put a `MarkdownPage.php` class in there: ```php Date: Tue, 8 Jul 2025 22:30:04 +0200 Subject: [PATCH 157/314] start rework for 2025 --- 01-front-controller.md | 54 +- 02-composer.md | 27 +- 03-error-handler.md | 110 +- app/public/favicon.ico | Bin 15086 -> 0 bytes implementation/01/public/index.php | 5 + implementation/01/src/Bootstrap.php | 5 + implementation/02/composer.json | 19 + implementation/02/public/index.php | 5 + implementation/02/src/Bootstrap.php | 7 + implementation/03/composer.json | 10 +- implementation/03/public/favicon.ico | Bin 15086 -> 0 bytes implementation/03/public/index.php | 6 +- implementation/03/src/Bootstrap.php | 39 +- implementation/12/composer.lock | 1516 -------------------------- 14 files changed, 214 insertions(+), 1589 deletions(-) delete mode 100644 app/public/favicon.ico create mode 100644 implementation/01/public/index.php create mode 100644 implementation/01/src/Bootstrap.php create mode 100644 implementation/02/composer.json create mode 100644 implementation/02/public/index.php create mode 100644 implementation/02/src/Bootstrap.php delete mode 100644 implementation/03/public/favicon.ico delete mode 100644 implementation/12/composer.lock diff --git a/01-front-controller.md b/01-front-controller.md index 53d17ef..ed2c184 100644 --- a/01-front-controller.md +++ b/01-front-controller.md @@ -2,29 +2,45 @@ ### Front Controller -A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. +A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your +application. -To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. +To start, create an empty directory for your project. You also need an entry point where all requests will go to. This +means you will have to create an `index.php` file. -A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. +A common way to do this is to just put the `index.php` in the root folder of the projects. Let me explain why you should not do this. -The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. +The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server +has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders +where your application files are. -But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. +But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your +whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. -So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. +So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` +folder for your application, also in the project root folder. -Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: +Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so +put just the following code in there: ```php -getCode()) { + E_ERROR, E_USER_ERROR => 'Fatal Error', + E_WARNING, E_USER_WARNING => 'Warning', + E_NOTICE, E_USER_NOTICE => 'Notice', + default => 'Unknown Error' + }; + + echo <<{$errorType} +

{$t->getMessage()}

+
{$t->getTraceAsString()}
+ HTML; +}); + +set_error_handler( + function (int $errno, string $errstr, string $errfile, int $errline) { + throw new ErrorException( + message: $errstr, + code: $errno, + severity: $errno, + filename: $errfile, + line: $errline + ); + } +); + +echo 'Hello world!'; +``` + +You can then replace `echo 'Hello world!';` with `trigger_error('This is a test error');` +or `throw new Exception('This is a test exception');` and open it in your browser to see if the error handling works. + + +During development there are some other nice features to add. For example a quick link to open your Editor on the file the Error occured. So the first package for your application will take care of that. I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, you have total control over your project. -An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) +Some alternatives would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) or [Tracy](https://tracy.nette.org/en/) -To install a new package, open up your `composer.json` and add the package to the require part. It should now look -like this: - -```php -"require": { - "php": ">=8.1.0", - "filp/whoops": "^2.14" -}, -``` - -Now run `composer update` in your console, and it will be installed. - -Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, -i that case composer automatically installs the package and updates your composer.json-file. +To install that package into your project simply type `composer require filp/whoops` into your terminal at the project root, +now composer automatically looks for a version of that package compatible with the rest of your project and your php +version. But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you @@ -36,7 +75,7 @@ only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Boots can help someone to gain access to your system. Always show a user-friendly error page instead and send an email to yourself, write to a log or something similar. So only you can see the errors in the production environment. -For development that does not make sense though -- you want a nice error page. The solution is to have an environment +For development that does not make sense, though -- you want a nice error page. The solution is to have an environment switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in case no environment has been set. @@ -44,36 +83,51 @@ Then after the error handler registration, throw an `Exception` to test if every Your `Bootstrap.php` should now look similar to this: ```php -pushHandler( + new CallbackHandler( + function (Throwable $e) use ($environment) { + if ($environment !== 'dev') { + http_response_code(500); + echo 'Whoops'; + } + error_log(<<getMessage()} + {$e->getTraceAsString()} + TXT + ); + } + ) +); + if ($environment === 'dev') { $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (\Throwable $e) { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); } $whoops->register(); -throw new \Exception("Ooooopsie"); +throw new \Exception('Hello world'); ``` You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until you get it working. Now would also be a good time for another commit. +**Side-note:** Here we use `getenv()` to read a Variable from the Environment and fallback to using 'dev' if it is not set. That is a bad default, and in a production app you should always default to the strictest mode. +There are also good libraries that help with managing that in a better fashion. You can take a look at [phpdotenv](https://github.com/vlucas/phpdotenv) or [symfony/dotenv](https://github.com/symfony/dotenv) and maybe implement them. This tutorial however skips this step. [<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/app/public/favicon.ico b/app/public/favicon.ico deleted file mode 100644 index 09499b8b3b3201e0f50088e3ac42e167778d1153..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/01/public/index.php b/implementation/01/public/index.php new file mode 100644 index 0000000..64740cd --- /dev/null +++ b/implementation/01/public/index.php @@ -0,0 +1,5 @@ +=8.1", - "filp/whoops": "^2.14" + "php": "^8.4", + "filp/whoops": "^2.18" } } diff --git a/implementation/03/public/favicon.ico b/implementation/03/public/favicon.ico deleted file mode 100644 index 09499b8b3b3201e0f50088e3ac42e167778d1153..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/03/public/index.php b/implementation/03/public/index.php index 970d132..d93da3a 100644 --- a/implementation/03/public/index.php +++ b/implementation/03/public/index.php @@ -1,3 +1,5 @@ -pushHandler( + new CallbackHandler( + function (Throwable $e) use ($environment) { + if ($environment !== 'dev') { + http_response_code(500); + echo 'Whoops'; + } + error_log(<<getMessage()} + {$e->getTraceAsString()} + TXT + ); + } + ) +); if ($environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler()); -} else { - $whoops->pushHandler(function (\Throwable $t) { - error_log('ERROR: ' . $t->getMessage(), $t->getCode()); - echo 'Oooopsie'; - }); + $whoops->pushHandler(new PrettyPageHandler); } - $whoops->register(); -echo 'Hello World!'; +throw new \Exception('Hello world'); \ No newline at end of file diff --git a/implementation/12/composer.lock b/implementation/12/composer.lock deleted file mode 100644 index c3de49e..0000000 --- a/implementation/12/composer.lock +++ /dev/null @@ -1,1516 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "ea5b586e05e6b1d75bded814662047b4", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.11.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", - "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2022-05-17T10:57:52+00:00" - }, - { - "name": "laravel/serializable-closure", - "version": "v1.2.0", - "source": { - "type": "git", - "url": "https://github.com/laravel/serializable-closure.git", - "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", - "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", - "shasum": "" - }, - "require": { - "php": "^7.3|^8.0" - }, - "require-dev": { - "pestphp/pest": "^1.18", - "phpstan/phpstan": "^0.12.98", - "symfony/var-dumper": "^5.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Laravel\\SerializableClosure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - }, - { - "name": "Nuno Maduro", - "email": "nuno@laravel.com" - } - ], - "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", - "keywords": [ - "closure", - "laravel", - "serializable" - ], - "support": { - "issues": "https://github.com/laravel/serializable-closure/issues", - "source": "https://github.com/laravel/serializable-closure" - }, - "time": "2022-05-16T17:09:47+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.4.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/ae0f1b3b03d8b29dff81747063cbfd6276246cc4", - "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4", - "shasum": "" - }, - "require": { - "laravel/serializable-closure": "^1.0", - "php": ">=7.4.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.10", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.11.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^9.5" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.4.0" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2022-04-09T16:46:38+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "1.5.1", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "981cc368a216c988e862a75e526b6076987d1b50" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", - "reference": "981cc368a216c988e862a75e526b6076987d1b50", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.5", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5", - "symfony/process": "^5.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" - }, - "time": "2022-05-05T11:32:40+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.6.8", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", - "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.6.8" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-05-10T06:54:21+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.2.3", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", - "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.6.3" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" - }, - "time": "2022-05-04T15:20:40+00:00" - }, - { - "name": "rector/rector", - "version": "0.12.23", - "source": { - "type": "git", - "url": "https://github.com/rectorphp/rector.git", - "reference": "690b31768b322db886b35845f8452025eba2cacb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", - "reference": "690b31768b322db886b35845f8452025eba2cacb", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0", - "phpstan/phpstan": "^1.6" - }, - "conflict": { - "phpstan/phpdoc-parser": "<1.2", - "rector/rector-cakephp": "*", - "rector/rector-doctrine": "*", - "rector/rector-laravel": "*", - "rector/rector-nette": "*", - "rector/rector-phpoffice": "*", - "rector/rector-phpunit": "*", - "rector/rector-prefixed": "*", - "rector/rector-symfony": "*" - }, - "bin": [ - "bin/rector" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "0.12-dev" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Instant Upgrade and Automated Refactoring of any PHP code", - "support": { - "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/0.12.23" - }, - "funding": [ - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2022-05-01T15:50:16+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "7.2.0", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", - "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.2 || ^8.0", - "phpstan/phpdoc-parser": "^1.5.1", - "squizlabs/php_codesniffer": "^3.6.2" - }, - "require-dev": { - "phing/phing": "2.17.3", - "php-parallel-lint/php-parallel-lint": "1.3.2", - "phpstan/phpstan": "1.4.10|1.6.7", - "phpstan/phpstan-deprecation-rules": "1.0.0", - "phpstan/phpstan-phpunit": "1.0.0|1.1.1", - "phpstan/phpstan-strict-rules": "1.2.3", - "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "7.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2022-05-06T10:58:42+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.8", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", - "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-04-26T13:22:23+00:00" - }, - { - "name": "symplify/easy-coding-standard", - "version": "10.2.6", - "source": { - "type": "git", - "url": "https://github.com/symplify/easy-coding-standard.git", - "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", - "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "conflict": { - "friendsofphp/php-cs-fixer": "<3.0", - "squizlabs/php_codesniffer": "<3.6" - }, - "bin": [ - "bin/ecs" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "9.5-dev" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Prefixed scoped version of ECS package", - "support": { - "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" - }, - "funding": [ - { - "url": "https://www.paypal.me/rectorphp", - "type": "custom" - }, - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2022-05-17T07:11:50+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.3.0" -} From 513abac66be3730c6eb0ac104bc3b002b86a8886 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 16 Sep 2014 21:12:54 +0200 Subject: [PATCH 158/314] added content to the controller part --- 6-controllers.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/6-controllers.md b/6-controllers.md index 8bcd605..a9b534d 100644 --- a/6-controllers.md +++ b/6-controllers.md @@ -4,4 +4,52 @@ When I talk about a controller in this tutorial then I am just referring to a class that has handler methods. I am not talking about [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html) controllers. MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). +Create a new folder inside the `src/` folder with the name `HelloWorld`. This will be where all your hello world related code will end up in. In there, create `HelloWorldController.php`. + +``` +$method($vars); + break; +``` + +So instead of just calling a handler method you are now instantiating the controller object and then calling the method on it. + +Now if you visit `http://localhost:8000/hello-world` everything should work. If not, go back and debug. And of course don't forget to commit your changes. + to be continued... From 50ead93c096843bf6336a6e5af1be0ed1110f3c1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 16 Sep 2014 21:14:29 +0200 Subject: [PATCH 159/314] prepared DI part and navigation --- 6-controllers.md | 4 ++-- 7-dependency-injection.md | 5 +++++ README.md | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 7-dependency-injection.md diff --git a/6-controllers.md b/6-controllers.md index a9b534d..1c0cb60 100644 --- a/6-controllers.md +++ b/6-controllers.md @@ -1,4 +1,4 @@ -[<< previous](5-router.md) +[<< previous](5-router.md) | [next >>](7-dependency-injection.md) ### Controllers @@ -52,4 +52,4 @@ So instead of just calling a handler method you are now instantiating the contro Now if you visit `http://localhost:8000/hello-world` everything should work. If not, go back and debug. And of course don't forget to commit your changes. -to be continued... +[<< previous](5-router.md) | [next >>](7-dependency-injection.md) diff --git a/7-dependency-injection.md b/7-dependency-injection.md new file mode 100644 index 0000000..530724d --- /dev/null +++ b/7-dependency-injection.md @@ -0,0 +1,5 @@ +[<< previous](5-router.md) | [next >>](7-dependency-injection.md) + +### Dependency Injection + +to be continued... \ No newline at end of file diff --git a/README.md b/README.md index c59e4ef..ee3483f 100644 --- a/README.md +++ b/README.md @@ -20,4 +20,4 @@ So let's get started right away with the [first part](1-front-controller.md). 4. [HTTP](4-http.md) 5. [Router](5-router.md) 6. [Controllers](6-controllers.md) -7. Dependency Injector +7. [Dependency Injection](7-dependency-injection.md) From 28827aaa358b33d06382963ce2ef2c3b498b88de Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 16 Sep 2014 21:16:49 +0200 Subject: [PATCH 160/314] fixed navigation --- 7-dependency-injection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-dependency-injection.md b/7-dependency-injection.md index 530724d..41ad002 100644 --- a/7-dependency-injection.md +++ b/7-dependency-injection.md @@ -1,4 +1,4 @@ -[<< previous](5-router.md) | [next >>](7-dependency-injection.md) +[<< previous](6-controllers.md) ### Dependency Injection From 453ca62f77e3f576adc64e38ed208e975cc7d503 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 16 Sep 2014 21:48:41 +0200 Subject: [PATCH 161/314] finished inversion of control part --- 6-controllers.md | 4 +-- 7-dependency-injection.md | 5 ---- 7-inversion-of-control.md | 51 +++++++++++++++++++++++++++++++++++++++ 9-dependency-injector.md | 5 ++++ README.md | 3 ++- 5 files changed, 60 insertions(+), 8 deletions(-) delete mode 100644 7-dependency-injection.md create mode 100644 7-inversion-of-control.md create mode 100644 9-dependency-injector.md diff --git a/6-controllers.md b/6-controllers.md index 1c0cb60..ca18500 100644 --- a/6-controllers.md +++ b/6-controllers.md @@ -1,4 +1,4 @@ -[<< previous](5-router.md) | [next >>](7-dependency-injection.md) +[<< previous](5-router.md) | [next >>](7-inversion-of-control.md) ### Controllers @@ -52,4 +52,4 @@ So instead of just calling a handler method you are now instantiating the contro Now if you visit `http://localhost:8000/hello-world` everything should work. If not, go back and debug. And of course don't forget to commit your changes. -[<< previous](5-router.md) | [next >>](7-dependency-injection.md) +[<< previous](5-router.md) | [next >>](7-inversion-of-control.md) \ No newline at end of file diff --git a/7-dependency-injection.md b/7-dependency-injection.md deleted file mode 100644 index 41ad002..0000000 --- a/7-dependency-injection.md +++ /dev/null @@ -1,5 +0,0 @@ -[<< previous](6-controllers.md) - -### Dependency Injection - -to be continued... \ No newline at end of file diff --git a/7-inversion-of-control.md b/7-inversion-of-control.md new file mode 100644 index 0000000..1146007 --- /dev/null +++ b/7-inversion-of-control.md @@ -0,0 +1,51 @@ +[<< previous](6-controllers.md) | [next >>](8-dependency-injector.md) + +### Inversion of Control + +In the last part you have set up a controller and were able to generate output with `echo`. But let's not forget that you have a nice object oriented HTTP abstraction available. But you still need to make it accessible inside the controller class. + +The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). + +If it sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is implemented things will make more sense. + +Change your `HelloWorldController` to the following: + +``` +response = $response; + } + + public function hello() + { + $this->response->setContent('Hello World'); + } +} +``` + +Please note that you are [importing](http://php.net/manual/en/language.namespaces.importing.php) `Http\Response` at the top of the file. This means that whenever you use `Response` inside this file, it will resolve to the fully qualified name. + +In the contructor you are now explicitly asking for a `Http\Response`. In this case, `Http\Response` is an interface. So any class that implements the interface can be injected. See [type hinting](http://php.net/manual/en/language.oop5.typehinting.php) and [interfaces](php.net/manual/en/language.oop5.interfaces.php) for reference. + +Now the code will result in an error because you are not actually injecting anything. So let's fix that in your `Bootstrap.php` where you dispatch when a route was found: + +``` +$class = new $className($response); +$class->$method($vars); +``` + +The `Http\HttpResponse` object implements the `Http\Response` interface, so it fulfills the contract and can be used. + +Now everything should work again. But if you follow this example, all your controllers will have the same objects injected. This is of course not good, so let's fix that in the next part. + +[<< previous](6-controllers.md) | [next >>](8-dependency-injector.md) \ No newline at end of file diff --git a/9-dependency-injector.md b/9-dependency-injector.md new file mode 100644 index 0000000..36f080d --- /dev/null +++ b/9-dependency-injector.md @@ -0,0 +1,5 @@ +[<< previous](7-inversion-of-control.md) + +### Inversion of Control + +to be continued... \ No newline at end of file diff --git a/README.md b/README.md index ee3483f..d805359 100644 --- a/README.md +++ b/README.md @@ -20,4 +20,5 @@ So let's get started right away with the [first part](1-front-controller.md). 4. [HTTP](4-http.md) 5. [Router](5-router.md) 6. [Controllers](6-controllers.md) -7. [Dependency Injection](7-dependency-injection.md) +7. [Inversion of Control](7-inversion-of-control.md) +8. [Dependency Injector](8-dependency-injector.md) From 2bad57036f7aa2ce77299cf823abee8119f7bdfa Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 16 Sep 2014 21:51:20 +0200 Subject: [PATCH 162/314] fixed the filename --- 9-dependency-injector.md => 8-dependency-injector.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename 9-dependency-injector.md => 8-dependency-injector.md (100%) diff --git a/9-dependency-injector.md b/8-dependency-injector.md similarity index 100% rename from 9-dependency-injector.md rename to 8-dependency-injector.md From bb0f40518a20950ab3a10d5be5855b5184980503 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 16 Sep 2014 21:52:08 +0200 Subject: [PATCH 163/314] fixed title --- 8-dependency-injector.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 36f080d..75f253a 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -1,5 +1,5 @@ [<< previous](7-inversion-of-control.md) -### Inversion of Control +### Dependency Injector to be continued... \ No newline at end of file From a3a401ce38a0a9df5d0c81b99107d8a3c9511daf Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 16 Sep 2014 21:53:11 +0200 Subject: [PATCH 164/314] fixed link --- 7-inversion-of-control.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-inversion-of-control.md b/7-inversion-of-control.md index 1146007..b836e22 100644 --- a/7-inversion-of-control.md +++ b/7-inversion-of-control.md @@ -35,7 +35,7 @@ class HelloWorldController Please note that you are [importing](http://php.net/manual/en/language.namespaces.importing.php) `Http\Response` at the top of the file. This means that whenever you use `Response` inside this file, it will resolve to the fully qualified name. -In the contructor you are now explicitly asking for a `Http\Response`. In this case, `Http\Response` is an interface. So any class that implements the interface can be injected. See [type hinting](http://php.net/manual/en/language.oop5.typehinting.php) and [interfaces](php.net/manual/en/language.oop5.interfaces.php) for reference. +In the contructor you are now explicitly asking for a `Http\Response`. In this case, `Http\Response` is an interface. So any class that implements the interface can be injected. See [type hinting](http://php.net/manual/en/language.oop5.typehinting.php) and [interfaces](http://php.net/manual/en/language.oop5.interfaces.php) for reference. Now the code will result in an error because you are not actually injecting anything. So let's fix that in your `Bootstrap.php` where you dispatch when a route was found: From f497f87f4f848d99d65b2f65dd94fe6b506cba7a Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 16 Sep 2014 21:53:59 +0200 Subject: [PATCH 165/314] removed partial sentence --- 3-error-handler.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/3-error-handler.md b/3-error-handler.md index f3b887d..03c305c 100644 --- a/3-error-handler.md +++ b/3-error-handler.md @@ -23,8 +23,6 @@ Now run `composer update` in your console and it will be installed. But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you only have to add a `require '../vendor/autoload.php';` to your `Bootstrap.php`. -Now before you start adding the error handler code to the - **Important:** Never show any errors in your production environment. A stack trace or even just a simple error message can help someone to gain access to your system. Always show a user friendly error page instead and send an email to yourself, write to a log or something similar. So only you can see the errors in the production environment. For development that does not make sense though -- you want a nice error page. The solution is to have an environment switch in your code. For now you can just set it to `development`. From cefc80e8c1e1f1c14800bbfd6c15097325d269ff Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 17 Sep 2014 21:22:11 +0200 Subject: [PATCH 166/314] started di part --- 8-dependency-injector.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 75f253a..f942025 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -2,4 +2,10 @@ ### Dependency Injector -to be continued... \ No newline at end of file +A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when the class is instantiated. + +My favorite injector is [Auryn](https://github.com/rdlowrey/Auryn), so install `rdlowrey/auryn` with composer or use one of the alternatives below: + +[Pimple](http://pimple.sensiolabs.org/), [Orno DI](https://github.com/orno/di), [PHP-DI](https://github.com/mnapoli/PHP-DI) + +to be continued... From 624cb154983f7daf85bfe6aeff0cfa4a001f21ca Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 17 Sep 2014 21:46:06 +0200 Subject: [PATCH 167/314] added content to di part --- 8-dependency-injector.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index f942025..2ee20e7 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -8,4 +8,40 @@ My favorite injector is [Auryn](https://github.com/rdlowrey/Auryn), so install ` [Pimple](http://pimple.sensiolabs.org/), [Orno DI](https://github.com/orno/di), [PHP-DI](https://github.com/mnapoli/PHP-DI) +Create a new file called `Dependencies.php` in your `src/` folder. In there add the following content: + +``` +share('Http\CookieBuilder'); +$injector->delegate('Http\CookieBuilder', function($environment){ + $cookieBuilder = new \Http\CookieBuilder; + $cookieBuilder->setDefaultSecure($environment === 'production'); + return $cookieBuilder; +}); + +$injector->alias('Http\Response', 'Http\HttpResponse'); +$injector->share('Http\HttpRequest'); +$injector->define('Http\HttpRequest', [ + ':get' => $_GET, + ':post' => $_POST, + ':cookies' => $_COOKIE, + ':files' => $_FILES, + ':server' => $_SERVER, +]); + +$injector->alias('Http\Request', 'Http\HttpRequest'); +$injector->share('Http\HttpResponse'); + +return $injector; +``` + +Make sure you understand what `alias`, `share` and `define` are doing before you continue. You can read about them in the [Auryn documentation](https://github.com/rdlowrey/Auryn). + +You are sharing the HTTP objects because there would not be much point in adding content to one object and then returning another one. So by sharing it you always get the same instance. + +The alias allows you to type hint the interface instead of the class name. This makes it easy to switch the implementation without having to go back and edit all your classes that use the old implementation. + to be continued... From 1328bce2c55f5475dd4a55624b94e184336a0abc Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 17 Sep 2014 22:05:09 +0200 Subject: [PATCH 168/314] expanded di part --- 8-dependency-injector.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 2ee20e7..8ef7e8a 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -10,7 +10,7 @@ My favorite injector is [Auryn](https://github.com/rdlowrey/Auryn), so install ` Create a new file called `Dependencies.php` in your `src/` folder. In there add the following content: -``` +```php make('Http\HttpRequest'); +$response = $injector->make('Http\HttpResponse'); +``` + +The other part that has to be changed is the dispatching of the route. Before you had the following code: + +```php +$class = new $className($response); +$class->$method($vars); +``` + +Change that to the following: + +```php +$class = $injector->make($className); +$class->$method($vars); +``` + +Now all your controller constructor dependencies will be automatically resolved with Auryn. + to be continued... From 562ddf720f5310780600d2898808b65d04451c4f Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 17 Sep 2014 22:06:16 +0200 Subject: [PATCH 169/314] added code formatting --- 6-controllers.md | 6 +++--- 7-inversion-of-control.md | 4 ++-- 8-dependency-injector.md | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/6-controllers.md b/6-controllers.md index ca18500..99a41a1 100644 --- a/6-controllers.md +++ b/6-controllers.md @@ -6,7 +6,7 @@ When I talk about a controller in this tutorial then I am just referring to a cl Create a new folder inside the `src/` folder with the name `HelloWorld`. This will be where all your hello world related code will end up in. In there, create `HelloWorldController.php`. -``` +```php $method($vars); ``` diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 8ef7e8a..2290598 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -5,7 +5,6 @@ A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when the class is instantiated. My favorite injector is [Auryn](https://github.com/rdlowrey/Auryn), so install `rdlowrey/auryn` with composer or use one of the alternatives below: - [Pimple](http://pimple.sensiolabs.org/), [Orno DI](https://github.com/orno/di), [PHP-DI](https://github.com/mnapoli/PHP-DI) Create a new file called `Dependencies.php` in your `src/` folder. In there add the following content: From 76874305497ecb51310e271b4e5da696c75b2693 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 17 Sep 2014 22:07:26 +0200 Subject: [PATCH 170/314] added code formatting --- 1-front-controller.md | 4 ++-- 2-composer.md | 4 ++-- 3-error-handler.md | 4 ++-- 4-http.md | 10 +++++----- 5-router.md | 6 +++--- 8-dependency-injector.md | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/1-front-controller.md b/1-front-controller.md index 413924c..4062204 100644 --- a/1-front-controller.md +++ b/1-front-controller.md @@ -16,7 +16,7 @@ So instead of doing that, create a folder in your project folder called `public` Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: -``` +```php =5.5.0", "filp/whoops": ">=1.1.2" @@ -29,7 +29,7 @@ For development that does not make sense though -- you want a nice error page. T Then after the error handler registration, throw an `Exception` to test if everything is working correctly. Your `Bootstrap.php` should now look similar to this: -``` +```php =5.5.0", "filp/whoops": ">=1.1.2", @@ -24,7 +24,7 @@ Again, edit the `composer.json` to add the new component and then run `composer Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): -``` +```php $request = new \Http\HttpRequest($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER); $response = new \Http\HttpResponse; ``` @@ -33,7 +33,7 @@ This sets up the `Request` and `Response` objects that you can use in your other To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: -``` +```php foreach ($response->getHeaders() as $header) { header($header); } @@ -45,14 +45,14 @@ This will send the response data to the browser. If you don't do this, nothing h Right now it is just sending an empty response back to the browser with the status code `200`; to change that, add the following code between the code snippets from above: -``` +```php $content = '

Hello World

'; $response->setContent($content); ``` If you want to try a 404 error, use the following code: -``` +```php $response->setContent('404 - Page not found'); $response->setStatusCode(404); ``` diff --git a/5-router.md b/5-router.md index f59be70..b559a51 100644 --- a/5-router.md +++ b/5-router.md @@ -14,7 +14,7 @@ By now you know how to install Composer packages, so I will leave that to you. Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last part. -``` +```php $dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { $r->addRoute('GET', '/hello-world', function () { echo 'Hello World'; @@ -48,7 +48,7 @@ This setup might work for really small applications, but once you start adding a Create a `Routes.php` file in the `src/` folder. It should look like this: -``` +```php Date: Wed, 17 Sep 2014 22:14:25 +0200 Subject: [PATCH 171/314] switched from uri to path --- 4-http.md | 2 +- 5-router.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/4-http.md b/4-http.md index 8229b76..0b148aa 100644 --- a/4-http.md +++ b/4-http.md @@ -18,7 +18,7 @@ Again, edit the `composer.json` to add the new component and then run `composer "require": { "php": ">=5.5.0", "filp/whoops": ">=1.1.2", - "patricklouys/http": ">=1.0.2" + "patricklouys/http": ">=1.0.3" }, ``` diff --git a/5-router.md b/5-router.md index b559a51..764d318 100644 --- a/5-router.md +++ b/5-router.md @@ -24,7 +24,7 @@ $dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r }); }); -$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri()); +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getPath()); switch ($routeInfo[0]) { case \FastRoute\Dispatcher::NOT_FOUND: $response->setContent('404 - Page not found'); From b2379fb97e15604c0c4788d511bde08ce231a2b0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 17 Sep 2014 22:45:17 +0200 Subject: [PATCH 172/314] changed to correct version number --- 4-http.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/4-http.md b/4-http.md index 0b148aa..0cbb5fa 100644 --- a/4-http.md +++ b/4-http.md @@ -18,7 +18,7 @@ Again, edit the `composer.json` to add the new component and then run `composer "require": { "php": ">=5.5.0", "filp/whoops": ">=1.1.2", - "patricklouys/http": ">=1.0.3" + "patricklouys/http": ">=1.1.0" }, ``` From 3f383f8f2590a05811cdc48c3f5fc1f7b1bd82a2 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 18 Sep 2014 21:53:50 +0200 Subject: [PATCH 173/314] finished DI part --- 8-dependency-injector.md | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 1cfef96..110332a 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -68,4 +68,38 @@ $class->$method($vars); Now all your controller constructor dependencies will be automatically resolved with Auryn. -to be continued... +Go back to your `HelloWorldController.php` and change it to the following: + +```php +request = $request; + $this->response = $response; + } + + public function hello() + { + $content = '

Hello World

'; + $content .= 'Hello ' . $this->request->getParameter('name', 'stranger'); + $this->response->setContent($content); + } +} +``` + +As you can see now the class has two dependencies. Try to access the page with a GET parameter like this `http://localhost:8000/hello-world?name=Arthur%20Dent`. + +Congratulations, you have now successfully laid the groundwork for your application. + +[<< previous](7-inversion-of-control.md) \ No newline at end of file From 85f6c5bf1c0cf5a0a8baf3f6705aa0dc8f1d7201 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 19 Sep 2014 15:50:19 +0200 Subject: [PATCH 174/314] changed di recommendation --- 8-dependency-injector.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 110332a..ef7e2d1 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -4,8 +4,7 @@ A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when the class is instantiated. -My favorite injector is [Auryn](https://github.com/rdlowrey/Auryn), so install `rdlowrey/auryn` with composer or use one of these alternatives: -[Pimple](http://pimple.sensiolabs.org/), [Orno DI](https://github.com/orno/di), [PHP-DI](https://github.com/mnapoli/PHP-DI) +There is only one injector that I can recommend: [Auryn](https://github.com/rdlowrey/Auryn). Sadly all the alternatives that I am aware of are using the [service locator antipattern](http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/). Create a new file called `Dependencies.php` in your `src/` folder. In there add the following content: From 0a21e87f95a5e57fd1a387c1b61f0306719543f2 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 20 Sep 2014 19:56:08 +0200 Subject: [PATCH 175/314] Changed former controller part --- 5-router.md | 4 +-- 6-controllers.md | 55 --------------------------------------- 7-inversion-of-control.md | 4 +-- README.md | 2 +- 4 files changed, 5 insertions(+), 60 deletions(-) delete mode 100644 6-controllers.md diff --git a/5-router.md b/5-router.md index 764d318..2bb5c59 100644 --- a/5-router.md +++ b/5-router.md @@ -1,4 +1,4 @@ -[<< previous](4-http.md) | [next >>](6-controllers.md) +[<< previous](4-http.md) | [next >>](6-dispatching-to-a-class.md) ### Router @@ -76,4 +76,4 @@ $dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); This is already an improvement, but now all the handler code is in the `Routers.php` file. This is not optimal, so let's fix that in the next part. -[<< previous](4-http.md) | [next >>](6-controllers.md) +[<< previous](4-http.md) | [next >>](6-dispatching-to-a-class.md) diff --git a/6-controllers.md b/6-controllers.md deleted file mode 100644 index 99a41a1..0000000 --- a/6-controllers.md +++ /dev/null @@ -1,55 +0,0 @@ -[<< previous](5-router.md) | [next >>](7-inversion-of-control.md) - -### Controllers - -When I talk about a controller in this tutorial then I am just referring to a class that has handler methods. I am not talking about [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html) controllers. MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). - -Create a new folder inside the `src/` folder with the name `HelloWorld`. This will be where all your hello world related code will end up in. In there, create `HelloWorldController.php`. - -```php -$method($vars); - break; -``` - -So instead of just calling a handler method you are now instantiating the controller object and then calling the method on it. - -Now if you visit `http://localhost:8000/hello-world` everything should work. If not, go back and debug. And of course don't forget to commit your changes. - -[<< previous](5-router.md) | [next >>](7-inversion-of-control.md) \ No newline at end of file diff --git a/7-inversion-of-control.md b/7-inversion-of-control.md index 2454758..7f255e7 100644 --- a/7-inversion-of-control.md +++ b/7-inversion-of-control.md @@ -1,4 +1,4 @@ -[<< previous](6-controllers.md) | [next >>](8-dependency-injector.md) +[<< previous](6-dispatching-to-a-class.md) | [next >>](8-dependency-injector.md) ### Inversion of Control @@ -48,4 +48,4 @@ The `Http\HttpResponse` object implements the `Http\Response` interface, so it f Now everything should work again. But if you follow this example, all your controllers will have the same objects injected. This is of course not good, so let's fix that in the next part. -[<< previous](6-controllers.md) | [next >>](8-dependency-injector.md) \ No newline at end of file +[<< previous](6-dispatching-to-a-class.md) | [next >>](8-dependency-injector.md) \ No newline at end of file diff --git a/README.md b/README.md index d805359..d921ed6 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,6 @@ So let's get started right away with the [first part](1-front-controller.md). 3. [Error Handler](3-error-handler.md) 4. [HTTP](4-http.md) 5. [Router](5-router.md) -6. [Controllers](6-controllers.md) +6. [Dispatching to a Class](6-dispatching-to-a-class.md) 7. [Inversion of Control](7-inversion-of-control.md) 8. [Dependency Injector](8-dependency-injector.md) From f018744de5e04a75b3b0e7adf5093202f2850187 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 20 Sep 2014 19:56:51 +0200 Subject: [PATCH 176/314] Changed former controller part --- 6-dispatching-to-a-class.md | 57 +++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 6-dispatching-to-a-class.md diff --git a/6-dispatching-to-a-class.md b/6-dispatching-to-a-class.md new file mode 100644 index 0000000..31f2fbe --- /dev/null +++ b/6-dispatching-to-a-class.md @@ -0,0 +1,57 @@ +[<< previous](5-router.md) | [next >>](7-inversion-of-control.md) + +### Dispatching to a Class + +In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). + +Instead of just calling everything a controller, let's give our names descriptive names that describe what the class actually does. In this case, we will just display content, so a fitting name would be `Presenter`. If the class does something else, we will name it accordingly. + +Create a new folder inside the `src/` folder with the name `HelloWorld`. This will be where all your hello world related code will end up in. In there, create `HelloWorldPresenter.php`. + +```php +$method($vars); + break; +``` + +So instead of just calling a method you are now instantiating an object and then calling the method on it. + +Now if you visit `http://localhost:8000/hello-world` everything should work. If not, go back and debug. And of course don't forget to commit your changes. + +[<< previous](5-router.md) | [next >>](7-inversion-of-control.md) \ No newline at end of file From 7cb97d52adb4930316301a544cfc4b17b0899cc8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 27 Sep 2014 16:58:34 +0200 Subject: [PATCH 177/314] refactored to match earlier changes in different part --- 8-dependency-injector.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index ef7e2d1..43a3657 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -67,7 +67,7 @@ $class->$method($vars); Now all your controller constructor dependencies will be automatically resolved with Auryn. -Go back to your `HelloWorldController.php` and change it to the following: +Go back to your `HelloWorldPresenter.php` and change it to the following: ```php Date: Sun, 5 Oct 2014 21:03:11 +0200 Subject: [PATCH 178/314] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d921ed6..e88b86a 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,4 @@ So let's get started right away with the [first part](1-front-controller.md). 6. [Dispatching to a Class](6-dispatching-to-a-class.md) 7. [Inversion of Control](7-inversion-of-control.md) 8. [Dependency Injector](8-dependency-injector.md) +9. tbd... From 6f608efc240924dad43efc5fdb57907545a90ec6 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 6 Oct 2014 22:48:28 +0200 Subject: [PATCH 179/314] added next topic --- 8-dependency-injector.md | 4 ++-- 8-templating.md | 7 +++++++ README.md | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 8-templating.md diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 43a3657..1ea1e7d 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -1,4 +1,4 @@ -[<< previous](7-inversion-of-control.md) +[<< previous](7-inversion-of-control.md) | [next >>](9-templating.md ### Dependency Injector @@ -101,4 +101,4 @@ As you can see now the class has two dependencies. Try to access the page with a Congratulations, you have now successfully laid the groundwork for your application. -[<< previous](7-inversion-of-control.md) \ No newline at end of file +[<< previous](7-inversion-of-control.md) | [next >>](9-templating.md \ No newline at end of file diff --git a/8-templating.md b/8-templating.md new file mode 100644 index 0000000..e038b5a --- /dev/null +++ b/8-templating.md @@ -0,0 +1,7 @@ +[<< previous](8-dependency-injector.md) + +### Templating + +coming soon... + +[<< previous](8-dependency-injector.md) \ No newline at end of file diff --git a/README.md b/README.md index e88b86a..650f42d 100644 --- a/README.md +++ b/README.md @@ -22,4 +22,4 @@ So let's get started right away with the [first part](1-front-controller.md). 6. [Dispatching to a Class](6-dispatching-to-a-class.md) 7. [Inversion of Control](7-inversion-of-control.md) 8. [Dependency Injector](8-dependency-injector.md) -9. tbd... +9. [Templating](9-templating.md) From 377f591ffa4edaf5dd764a6f728ad0178b993956 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 7 Oct 2014 22:59:11 +0200 Subject: [PATCH 180/314] into templating --- 8-templating.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/8-templating.md b/8-templating.md index e038b5a..fcab06b 100644 --- a/8-templating.md +++ b/8-templating.md @@ -2,6 +2,8 @@ ### Templating -coming soon... +A template engine is not necessary with PHP because the language itself can take care of that. But it can make things like escaping values easier. They also make it easier to draw a clear line between your application logic and the template files which should only put your variables into the HTML code. + +to be continued... [<< previous](8-dependency-injector.md) \ No newline at end of file From 427a35a690ba023bd38a6ab127b36526e1576773 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 7 Oct 2014 23:05:08 +0200 Subject: [PATCH 181/314] fixed filename --- 8-templating.md => 9-templating.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename 8-templating.md => 9-templating.md (100%) diff --git a/8-templating.md b/9-templating.md similarity index 100% rename from 8-templating.md rename to 9-templating.md From 042f656cc192f0708a11a65df57a9dce0caaf4a1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 7 Oct 2014 23:05:55 +0200 Subject: [PATCH 182/314] fixed links --- 8-dependency-injector.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 1ea1e7d..8c3fac4 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -1,4 +1,4 @@ -[<< previous](7-inversion-of-control.md) | [next >>](9-templating.md +[<< previous](7-inversion-of-control.md) | [next >>](9-templating.md) ### Dependency Injector @@ -101,4 +101,4 @@ As you can see now the class has two dependencies. Try to access the page with a Congratulations, you have now successfully laid the groundwork for your application. -[<< previous](7-inversion-of-control.md) | [next >>](9-templating.md \ No newline at end of file +[<< previous](7-inversion-of-control.md) | [next >>](9-templating.md) \ No newline at end of file From e393b38a289b4417f185d86db51bc1530d4f99d1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 8 Oct 2014 19:08:26 +0200 Subject: [PATCH 183/314] added to intro --- 9-templating.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/9-templating.md b/9-templating.md index fcab06b..6cb00ea 100644 --- a/9-templating.md +++ b/9-templating.md @@ -4,6 +4,12 @@ A template engine is not necessary with PHP because the language itself can take care of that. But it can make things like escaping values easier. They also make it easier to draw a clear line between your application logic and the template files which should only put your variables into the HTML code. +A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). + +For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). + +Other well known alternatives would be [Twig](http://twig.sensiolabs.org/) or [Smarty](http://www.smarty.net/), but they are both pretty bloated and offer too much functionality for just a template engine. + to be continued... [<< previous](8-dependency-injector.md) \ No newline at end of file From 7bd8c08cc397f8fb870ca08ac68deb49a1ae8e33 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 9 Oct 2014 19:18:45 +0200 Subject: [PATCH 184/314] added missing sentence --- 8-dependency-injector.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 8c3fac4..7ad36f6 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -6,7 +6,7 @@ A dependency injector resolves the dependencies of your class and makes sure tha There is only one injector that I can recommend: [Auryn](https://github.com/rdlowrey/Auryn). Sadly all the alternatives that I am aware of are using the [service locator antipattern](http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/). -Create a new file called `Dependencies.php` in your `src/` folder. In there add the following content: +Install the Auryn package and then create a new file called `Dependencies.php` in your `src/` folder. In there add the following content: ```php Date: Fri, 10 Oct 2014 16:05:22 +0200 Subject: [PATCH 185/314] improved writing --- 7-inversion-of-control.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-inversion-of-control.md b/7-inversion-of-control.md index 7f255e7..8459237 100644 --- a/7-inversion-of-control.md +++ b/7-inversion-of-control.md @@ -2,7 +2,7 @@ ### Inversion of Control -In the last part you have set up a controller and were able to generate output with `echo`. But let's not forget that you have a nice object oriented HTTP abstraction available. But you still need to make it accessible inside the controller class. +In the last part you have set up a controller and generated output with `echo`. But let's not forget that you have a nice object oriented HTTP abstraction available. But right now it's not accessible inside your class. The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). From 2cec4fe162c0164ecf54f8ece8d514ce36a47783 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Sat, 11 Oct 2014 18:12:58 +0200 Subject: [PATCH 186/314] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 650f42d..422dd74 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ### Introduction -If you are really new to the language, this is not for you. This tutorial is aimed at people who have grasped the basics of PHP and know a little bit about object-oriented programming. +If you are new to the language, this tutorial is not for you. This tutorial is aimed at people who have grasped the basics of PHP and know a little bit about object-oriented programming. You should at least heard of [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29). If you are not familiar with it, now would be a good time to familiarize yourself with the principles before you start with the tutorial. From ebadf5d24babe0e88bf32ae276ab00f56511ffe8 Mon Sep 17 00:00:00 2001 From: Chris Wright Date: Thu, 6 Nov 2014 11:30:52 +0000 Subject: [PATCH 187/314] Always use absolute paths for includes It's not safe to assume that the web server will always give you a sane cwd. --- 1-front-controller.md | 2 +- 3-error-handler.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/1-front-controller.md b/1-front-controller.md index 4062204..9814ad2 100644 --- a/1-front-controller.md +++ b/1-front-controller.md @@ -19,7 +19,7 @@ Inside the `public` folder you can now create your `index.php`. Remember that yo ```php Date: Thu, 6 Nov 2014 20:47:22 +0100 Subject: [PATCH 188/314] fixed typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 422dd74..0a381a7 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ If you are new to the language, this tutorial is not for you. This tutorial is aimed at people who have grasped the basics of PHP and know a little bit about object-oriented programming. -You should at least heard of [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29). If you are not familiar with it, now would be a good time to familiarize yourself with the principles before you start with the tutorial. +You should have at least heard of [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29). If you are not familiar with it, now would be a good time to familiarize yourself with the principles before you start with the tutorial. I saw a lot of people coming into the Stack Overflow PHP chatroom and asking if framework X is any good. Most of the time the answer was that they should just use PHP and not a framework to build their application. But many are overwhelmed by this and don't know where to start. From 034b4b6b6b4e71ec1001d7d97d45ac50c38dad60 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 6 Nov 2014 20:47:42 +0100 Subject: [PATCH 189/314] added link to alternative opinion --- 9-templating.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-templating.md b/9-templating.md index 6cb00ea..2917f06 100644 --- a/9-templating.md +++ b/9-templating.md @@ -4,7 +4,7 @@ A template engine is not necessary with PHP because the language itself can take care of that. But it can make things like escaping values easier. They also make it easier to draw a clear line between your application logic and the template files which should only put your variables into the HTML code. -A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). +A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). From 49335d2fe5cfc1cf1132e9d0e8fb9a2d7e437030 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 6 Nov 2014 22:34:31 +0100 Subject: [PATCH 190/314] removed faulty code --- 8-dependency-injector.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/8-dependency-injector.md b/8-dependency-injector.md index 7ad36f6..d48ee35 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -13,13 +13,6 @@ Install the Auryn package and then create a new file called `Dependencies.php` i $injector = new \Auryn\Provider; -$injector->share('Http\CookieBuilder'); -$injector->delegate('Http\CookieBuilder', function($environment){ - $cookieBuilder = new \Http\CookieBuilder; - $cookieBuilder->setDefaultSecure($environment === 'production'); - return $cookieBuilder; -}); - $injector->alias('Http\Response', 'Http\HttpResponse'); $injector->share('Http\HttpRequest'); $injector->define('Http\HttpRequest', [ From d1714e6ae34182da60588061acbe32b2e93ace21 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 6 Nov 2014 23:17:13 +0100 Subject: [PATCH 191/314] intro adapter --- 9-templating.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/9-templating.md b/9-templating.md index 2917f06..20a52ed 100644 --- a/9-templating.md +++ b/9-templating.md @@ -6,10 +6,35 @@ A template engine is not necessary with PHP because the language itself can take A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. -For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). +For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install that package before you continue. Other well known alternatives would be [Twig](http://twig.sensiolabs.org/) or [Smarty](http://www.smarty.net/), but they are both pretty bloated and offer too much functionality for just a template engine. -to be continued... +Now please go and have a look at the source code of the [engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class does not implement an interface. + +You could just type hint against the concrete class. But the problem with this approach is that you create tight coupling. + +In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want to add functionality to the engine. You can't do that without going back and changing all your code that is tightly coupled. + +What we want is loose coupling. We will type hint against an interface that implements an interface. So if you need another implementation, you just implement that interface in your new class and inject the new class instead. + +Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). This sounds a lot more complicated than it is, so just follow along. + +First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. A class can extend multiple interfaces if necessary. + +So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a new folder in your `src/` folder with the name `Template` where you can put all the template related things. + +In there create a new interface `Renderable.php` that looks like this: + +```php + Date: Fri, 7 Nov 2014 00:17:34 +0100 Subject: [PATCH 192/314] finished templating chapter --- 9-templating.md | 101 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/9-templating.md b/9-templating.md index 20a52ed..4c05168 100644 --- a/9-templating.md +++ b/9-templating.md @@ -31,10 +31,109 @@ In there create a new interface `Renderable.php` that looks like this: namespace Example\Template; -interface Renderable +interface Engine { public function render($template, $data = []); } ``` +Now that this is sorted out, let's create the mustache adapter class. In the same folder, create the file `MustacheEngineAdapter.php` with the following content: + +```php +engine = $engine; + } + + public function render($template, $data = []) + { + return $this->engine->render($template, $data); + } +} +``` + +As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple and only fulfills the interface. + +Of course we also have to add a definition in our `Dependencies.php` file because otherwise the injector won't know which implementation he has to inject when you hint for the interface. Add this line: + +`$injector->alias('Example\Template\Engine', 'Example\Template\MustacheEngineAdapter');` + +Now in your `HelloWorldPresenter`, add the new dependency like this: + +```php +request = $request; + $this->response = $response; + $this->templateEngine = $templateEngine; + } + +... +``` + +As you can see I imported the engine with an alias. Without the full namespace it would be relatively unclear what a class does if it is just referenced by `Engine`. Also, another part of the application might also have a class with the name `Engine`. So to avoid that I give it a short and descriptive alias. + +We also have to rewrite the `hello` method. Please note that while we are just passing in a simple array, Mustache also gives you the option to pass in a view context object. We will go over this later, for now let's keep it as simple as possible. + +```php + public function hello() + { + $data = [ + 'name' => $this->request->getParameter('name', 'stranger'), + ]; + $content = $this->templateEngine->render('Hello {{name}}', $data); + $this->response->setContent($content); + } +``` + +Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. But what we want is template files, so let's go back and change that. + +To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the `Dependencies.php` file and add the following code: + +```php +$injector->define('Mustache_Engine', [ + ':options' => [ + 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__DIR__).'/templates'), + ], +]); +``` + +In your project root folder, create a `templates` folder. In there, create a folder `HelloWorld` and in there a file `Hello.mustache`. The content of the file should look like this: + +``` +

Hello World

+Hello {{ name }} +``` + +Now you can go back to your `HelloWorldPresenter` and change the render line to `$content = $this->templateEngine->render('HelloWorld/Hello', $data);` + +Navigate to the hello page in your browser to make sure everything works. And as always, don't forget to commit your changes. + [<< previous](8-dependency-injector.md) \ No newline at end of file From cd809124a6d4f1757a7319dd30d95a064575c254 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 7 Nov 2014 09:37:17 +0100 Subject: [PATCH 193/314] missing spaces --- 9-templating.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-templating.md b/9-templating.md index 4c05168..29f143c 100644 --- a/9-templating.md +++ b/9-templating.md @@ -120,7 +120,7 @@ To make this change we need to pass an options array to the `Mustache_Engine` co ```php $injector->define('Mustache_Engine', [ ':options' => [ - 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__DIR__).'/templates'), + 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__DIR__) . '/templates'), ], ]); ``` From 3dd32558a04068f85122dce16a9ad953ad634862 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 16 Nov 2014 20:04:42 +0100 Subject: [PATCH 194/314] this fixes #9 --- 7-inversion-of-control.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/7-inversion-of-control.md b/7-inversion-of-control.md index 8459237..ecfec07 100644 --- a/7-inversion-of-control.md +++ b/7-inversion-of-control.md @@ -2,13 +2,13 @@ ### Inversion of Control -In the last part you have set up a controller and generated output with `echo`. But let's not forget that you have a nice object oriented HTTP abstraction available. But right now it's not accessible inside your class. +In the last part you have set up a presenter class and generated output with `echo`. But let's not forget that you have a nice object oriented HTTP abstraction available. But right now it's not accessible inside your class. The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). If it sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is implemented things will make more sense. -Change your `HelloWorldController` to the following: +Change your `HelloWorldPresenter` to the following: ```php $method($vars); The `Http\HttpResponse` object implements the `Http\Response` interface, so it fulfills the contract and can be used. -Now everything should work again. But if you follow this example, all your controllers will have the same objects injected. This is of course not good, so let's fix that in the next part. +Now everything should work again. But if you follow this example, all your objects that are instantiated this way will have the same objects injected. This is of course not good, so let's fix that in the next part. [<< previous](6-dispatching-to-a-class.md) | [next >>](8-dependency-injector.md) \ No newline at end of file From fc4dea1873f3c949fc414050d7bb2c48de331939 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 30 Nov 2014 21:06:11 +0100 Subject: [PATCH 195/314] changed to controllers --- 6-dispatching-to-a-class.md | 21 ++++++++++----------- 7-inversion-of-control.md | 12 ++++++------ 8-dependency-injector.md | 10 +++++----- 9-templating.md | 14 +++++++------- 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/6-dispatching-to-a-class.md b/6-dispatching-to-a-class.md index 31f2fbe..52c96e9 100644 --- a/6-dispatching-to-a-class.md +++ b/6-dispatching-to-a-class.md @@ -2,20 +2,22 @@ ### Dispatching to a Class -In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). +In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) and the followup posts. -Instead of just calling everything a controller, let's give our names descriptive names that describe what the class actually does. In this case, we will just display content, so a fitting name would be `Presenter`. If the class does something else, we will name it accordingly. +So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). -Create a new folder inside the `src/` folder with the name `HelloWorld`. This will be where all your hello world related code will end up in. In there, create `HelloWorldPresenter.php`. +We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Controllers` because that will be familiar for the people coming from a framework background. You could also name them `Handlers`. + +Create a new folder inside the `src/` folder with the name `Controllers`.In this folder we will place all our controller classes. In there, create a `Homepage.php` file. ```php >](7-inversion-of-control.md) \ No newline at end of file diff --git a/7-inversion-of-control.md b/7-inversion-of-control.md index ecfec07..1192545 100644 --- a/7-inversion-of-control.md +++ b/7-inversion-of-control.md @@ -2,22 +2,22 @@ ### Inversion of Control -In the last part you have set up a presenter class and generated output with `echo`. But let's not forget that you have a nice object oriented HTTP abstraction available. But right now it's not accessible inside your class. +In the last part you have set up a controller class and generated output with `echo`. But let's not forget that you have a nice object oriented HTTP abstraction available. But right now it's not accessible inside your class. The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). If it sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is implemented things will make more sense. -Change your `HelloWorldPresenter` to the following: +Change your `Homepage` controller to the following: ```php response = $response; } - public function hello() + public function show() { $this->response->setContent('Hello World'); } } ``` -Please note that you are [importing](http://php.net/manual/en/language.namespaces.importing.php) `Http\Response` at the top of the file. This means that whenever you use `Response` inside this file, it will resolve to the fully qualified name. +Note that you are [importing](http://php.net/manual/en/language.namespaces.importing.php) `Http\Response` at the top of the file. This means that whenever you use `Response` inside this file, it will resolve to the fully qualified name. In the contructor you are now explicitly asking for a `Http\Response`. In this case, `Http\Response` is an interface. So any class that implements the interface can be injected. See [type hinting](http://php.net/manual/en/language.oop5.typehinting.php) and [interfaces](http://php.net/manual/en/language.oop5.interfaces.php) for reference. diff --git a/8-dependency-injector.md b/8-dependency-injector.md index d48ee35..f4939af 100644 --- a/8-dependency-injector.md +++ b/8-dependency-injector.md @@ -60,17 +60,17 @@ $class->$method($vars); Now all your controller constructor dependencies will be automatically resolved with Auryn. -Go back to your `HelloWorldPresenter.php` and change it to the following: +Go back to your `Homepage` controller and change it to the following: ```php response = $response; } - public function hello() + public function show() { $content = '

Hello World

'; $content .= 'Hello ' . $this->request->getParameter('name', 'stranger'); @@ -90,7 +90,7 @@ class HelloWorldPresenter } ``` -As you can see now the class has two dependencies. Try to access the page with a GET parameter like this `http://localhost:8000/hello-world?name=Arthur%20Dent`. +As you can see now the class has two dependencies. Try to access the page with a GET parameter like this `http://localhost:8000/?name=Arthur%20Dent`. Congratulations, you have now successfully laid the groundwork for your application. diff --git a/9-templating.md b/9-templating.md index 29f143c..50ccb93 100644 --- a/9-templating.md +++ b/9-templating.md @@ -68,18 +68,18 @@ Of course we also have to add a definition in our `Dependencies.php` file becaus `$injector->alias('Example\Template\Engine', 'Example\Template\MustacheEngineAdapter');` -Now in your `HelloWorldPresenter`, add the new dependency like this: +Now in your `Homepage` controller, add the new dependency like this: ```php $this->request->getParameter('name', 'stranger'), @@ -125,14 +125,14 @@ $injector->define('Mustache_Engine', [ ]); ``` -In your project root folder, create a `templates` folder. In there, create a folder `HelloWorld` and in there a file `Hello.mustache`. The content of the file should look like this: +In your project root folder, create a `templates` folder. In there, create a file `Homepage.mustache`. The content of the file should look like this: ```

Hello World

Hello {{ name }} ``` -Now you can go back to your `HelloWorldPresenter` and change the render line to `$content = $this->templateEngine->render('HelloWorld/Hello', $data);` +Now you can go back to your `Homepage` controller and change the render line to `$content = $this->templateEngine->render('Homepage', $data);` Navigate to the hello page in your browser to make sure everything works. And as always, don't forget to commit your changes. From ac63dfb8fecc9d00aeb81c5b760e2ee02da2ac93 Mon Sep 17 00:00:00 2001 From: Hari K T Date: Thu, 27 Nov 2014 22:38:41 +0530 Subject: [PATCH 196/314] aura/web , not aura/http . --- 4-http.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/4-http.md b/4-http.md index 0cbb5fa..d1ce904 100644 --- a/4-http.md +++ b/4-http.md @@ -8,7 +8,7 @@ These are good if you just want to get a small script up and running without muc Once again, you don't have to reinvent the wheel and just install a package. I decided to write my own [HTTP component](https://github.com/PatrickLouys/http) because I did not like the existing components, but you don't have to do the same. -Some alternatives: [Symfony HttpFoundation](https://github.com/symfony/HttpFoundation), [Nette HTTP Component](https://github.com/nette/http), [Aura Http](https://github.com/auraphp/Aura.Http), [sabre/http](https://github.com/fruux/sabre-http) +Some alternatives: [Symfony HttpFoundation](https://github.com/symfony/HttpFoundation), [Nette HTTP Component](https://github.com/nette/http), [Aura Web](https://github.com/auraphp/Aura.Web), [sabre/http](https://github.com/fruux/sabre-http) In this tutorial I will use my own HTTP component, but of course you can use whichever package you like most. Just change the code accordingly. From efa3aeceb5065c2c406fa8532e8b41f71a4044ea Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 30 Nov 2014 22:38:48 +0100 Subject: [PATCH 197/314] new chapter --- 10-dynamic-pages.md | 225 ++++++++++++++++++++++++++++++++++++++++++++ 9-templating.md | 8 +- 2 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 10-dynamic-pages.md diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md new file mode 100644 index 0000000..a416cec --- /dev/null +++ b/10-dynamic-pages.md @@ -0,0 +1,225 @@ +[<< previous](9-templating.md) + +### Dynamic Pages + +So far we only have a static page with not much functionality. Just having a hello world example is not very useful, so let's go beyond that and add some real functionality to our application. + +Our first feature will be dynamic pages generated from [markdown](http://en.wikipedia.org/wiki/Markdown) files. + +Create a `Page` controller with the following content: + +```php +pageFolder = $pageFolder; + } + + public function getContentBySlug($slug) + { + return 'I am a placeholder'; + } +} +``` + +As you can see we are requiring the page folder path as a constructor argument. This makes the class flexible and if we decide to move files or write unit tests for the class, we can easily change the location with the constructor argument. + +You could also put the page related things into it's own package and reuse it in different applications. Because we are not tightly coupling things, things are very flexible. + +Because PHP does not have the ability to type hint for scalar values (things like strings and integers), we have to manually check that `$pageFolder` is a string. If we don't do that, there might be a bug in the future that is hard to find if a wrong type is injected. By throwing an exception, this can be caught and debugged immediately. + +This will do for now. Let's create a template file for our pages with the name `Page.mustache` in the `templates` folder. For now just add `{{ content }}` in there. + +Add the following to your `Dependencies.php` file so that the application know which implementation to inject for our new interface. We also define the the `pageFolder` there. + +```php +$injector->define('Example\Page\FilePageReader', [ + ':pageFolder' => __DIR__ . '/../pages', +]); + +$injector->alias('Example\Page\PageReader', 'Example\Page\FilePageReader'); +$injector->share('Example\Page\FilePageReader'); +``` + + +Now go back to the `Page` controller and change the `show` method to the following: + +```php +public function show($params) +{ + $slug = $params['slug']; + $data['content'] = $this->pageReader->getContentBySlug($slug); + $html = $this->templateEngine->render('Page', $data); + $this->response->setContent($html); +} +``` + +To make this work, we will need to inject a `Response`, `TemplateEngine` and a `PageReader`. I will leave this to you as an exercise. Remember to `use` all the proper namespaces. Use the `Homepage` controller as a reference. + +Did you get everything to work? + +If not, this is how the beginning of your controller should look now: + +```php +response = $response; + $this->templateEngine = $templateEngine; + $this->pageReader = $pageReader; + } +... +``` + +So far so good, now let's make our `FilePageReader` actually do some work. + +Again, let's check first that the proper type was passed into the method: + +```php +public function getContentBySlug($slug) +{ + if (!is_string($slug)) { + throw new InvalidArgumentException('slug must be a string'); + } +} +``` + +We also need to be able to communicate that a page was not found. For this we can create a custom exception that we can catch later. In your `src/Page` folder, create a `InvalidPageException.php` file with this content: + +```php +pageFolder/$slug.md"; + +if(!file_exists($path)) { + throw new InvalidPageException($slug); +} + +return file_get_contents($path); +``` + +Now if you navigate to a page that does not exist, you should see an `InvalidPageException`. If a file exists, you should see the content. + +Of course showing the user an exception for an invalid URL does not make sense. So let's catch the exception and show a 404 error instead. + +Go to your `Page` controller and refactor the `show` method so that it looks like this: + +```php +public function show($params) +{ + $slug = $params['slug']; + + try { + $data['content'] = $this->pageReader->getContentBySlug($slug); + } catch (InvalidPageException $e) { + $this->response->setStatusCode(404); + return $this->response->setContent('404 - Page not found'); + } + + $html = $this->templateEngine->render('Page', $data); + $this->response->setContent($html); +} +``` + +Add this at the top of your file: +```php +use Example\Page\InvalidPageException; +``` + +It is important that you don't forget this step, otherwise it will try to catch the wrong exception (it's looking in the wrong namespace) and thus will never catch it. + +Try a few different URLs to check that everything is working as it should. If something is wrong, go back and debug it until it works. + +And as always, don't forget to commit your changes. +[<< previous](9-templating.md) \ No newline at end of file diff --git a/9-templating.md b/9-templating.md index 50ccb93..564e9c1 100644 --- a/9-templating.md +++ b/9-templating.md @@ -1,4 +1,4 @@ -[<< previous](8-dependency-injector.md) +[<< previous](8-dependency-injector.md) | [next >>](10-dynamic-pages.md) ### Templating @@ -108,8 +108,8 @@ We also have to rewrite the `show` method. Please note that while we are just pa $data = [ 'name' => $this->request->getParameter('name', 'stranger'), ]; - $content = $this->templateEngine->render('Hello {{name}}', $data); - $this->response->setContent($content); + $html = $this->templateEngine->render('Hello {{name}}', $data); + $this->response->setContent($html); } ``` @@ -136,4 +136,4 @@ Now you can go back to your `Homepage` controller and change the render line to Navigate to the hello page in your browser to make sure everything works. And as always, don't forget to commit your changes. -[<< previous](8-dependency-injector.md) \ No newline at end of file +[<< previous](8-dependency-injector.md) | [next >>](10-dynamic-pages.md) \ No newline at end of file From c82c57b0844d029c18853a60aceaa99230ab1089 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 30 Nov 2014 22:41:27 +0100 Subject: [PATCH 198/314] missing newline --- 10-dynamic-pages.md | 1 + 1 file changed, 1 insertion(+) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index a416cec..9d2b789 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -222,4 +222,5 @@ It is important that you don't forget this step, otherwise it will try to catch Try a few different URLs to check that everything is working as it should. If something is wrong, go back and debug it until it works. And as always, don't forget to commit your changes. + [<< previous](9-templating.md) \ No newline at end of file From d7a121ac4ac11056a8922bf77dd31def83b3de7a Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 30 Nov 2014 22:42:31 +0100 Subject: [PATCH 199/314] added link to new chapter --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0a381a7..03b3de6 100644 --- a/README.md +++ b/README.md @@ -23,3 +23,4 @@ So let's get started right away with the [first part](1-front-controller.md). 7. [Inversion of Control](7-inversion-of-control.md) 8. [Dependency Injector](8-dependency-injector.md) 9. [Templating](9-templating.md) +10. [Dynamic Pages](10-dynamic-pages.md) From 388e4b5e877d0c519caed6964b465ee2778e1af2 Mon Sep 17 00:00:00 2001 From: HamZa Date: Mon, 8 Dec 2014 11:42:21 +0100 Subject: [PATCH 200/314] Changed $woops to $whoops --- 3-error-handler.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/3-error-handler.md b/3-error-handler.md index cb84972..760ca47 100644 --- a/3-error-handler.md +++ b/3-error-handler.md @@ -43,15 +43,15 @@ $environment = 'development'; /** * Register the error handler */ -$woops = new \Whoops\Run; +$whoops = new \Whoops\Run; if ($environment !== 'production') { - $woops->pushHandler(new \Whoops\Handler\PrettyPageHandler); + $whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler); } else { - $woops->pushHandler(function($e){ + $whoops->pushHandler(function($e){ echo 'Friendly error page and send an email to the developer'; }); } -$woops->register(); +$whoops->register(); throw new \Exception; From 3d6607f864c8adb57d481dfa09d81e389c7d8a13 Mon Sep 17 00:00:00 2001 From: HamZa Date: Mon, 8 Dec 2014 11:53:46 +0100 Subject: [PATCH 201/314] #18 changed $woops to $whoops --- 3-error-handler.md | 1 - 1 file changed, 1 deletion(-) diff --git a/3-error-handler.md b/3-error-handler.md index 760ca47..baa2358 100644 --- a/3-error-handler.md +++ b/3-error-handler.md @@ -60,4 +60,3 @@ throw new \Exception; You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until you get it working. Now would also be a good time for another commit. [<< previous](2-composer.md) | [next >>](4-http.md) - From d26ffc7acf309a28f2b35588a7c4ac6a45cb3868 Mon Sep 17 00:00:00 2001 From: Hassan Althaf Date: Thu, 4 Dec 2014 11:13:08 +0530 Subject: [PATCH 202/314] Update 9-templating.md Change file name 'Renderable' to 'Engine' because the interface is not found by Auryn Auto Loader as it finds classes/interfaces by their file names. --- 9-templating.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/9-templating.md b/9-templating.md index 564e9c1..373303f 100644 --- a/9-templating.md +++ b/9-templating.md @@ -24,7 +24,7 @@ First let's define the interface that we want. Remember the [interface segregati So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a new folder in your `src/` folder with the name `Template` where you can put all the template related things. -In there create a new interface `Renderable.php` that looks like this: +In there create a new interface `Engine.php` that looks like this: ```php >](10-dynamic-pages.md) \ No newline at end of file +[<< previous](8-dependency-injector.md) | [next >>](10-dynamic-pages.md) From 7eb008261ee8bac3e35d55eda7fc3832e14937fe Mon Sep 17 00:00:00 2001 From: Madara Date: Wed, 14 Jan 2015 15:32:21 +0200 Subject: [PATCH 203/314] Add leading zeros so that files list in correct order Fix all links to new file names --- ...nt-controller.md => 01-front-controller.md | 4 ++-- 2-composer.md => 02-composer.md | 4 ++-- 3-error-handler.md => 03-error-handler.md | 4 ++-- 4-http.md => 04-http.md | 4 ++-- 5-router.md => 05-router.md | 4 ++-- ...a-class.md => 06-dispatching-to-a-class.md | 4 ++-- ...f-control.md => 07-inversion-of-control.md | 4 ++-- ...y-injector.md => 08-dependency-injector.md | 4 ++-- 9-templating.md => 09-templating.md | 4 ++-- 10-dynamic-pages.md | 4 ++-- README.md | 20 +++++++++---------- 11 files changed, 30 insertions(+), 30 deletions(-) rename 1-front-controller.md => 01-front-controller.md (97%) rename 2-composer.md => 02-composer.md (93%) rename 3-error-handler.md => 03-error-handler.md (96%) rename 4-http.md => 04-http.md (96%) rename 5-router.md => 05-router.md (95%) rename 6-dispatching-to-a-class.md => 06-dispatching-to-a-class.md (94%) rename 7-inversion-of-control.md => 07-inversion-of-control.md (93%) rename 8-dependency-injector.md => 08-dependency-injector.md (95%) rename 9-templating.md => 09-templating.md (97%) diff --git a/1-front-controller.md b/01-front-controller.md similarity index 97% rename from 1-front-controller.md rename to 01-front-controller.md index 9814ad2..349c513 100644 --- a/1-front-controller.md +++ b/01-front-controller.md @@ -1,4 +1,4 @@ -[next >>](2-composer.md) +[next >>](02-composer.md) ### Front Controller @@ -40,4 +40,4 @@ If there is an error, go back and try to fix it. If you only see a blank page, c Now would be a good time to commit your progress. If you are not already using Git, set up a repository now. This is not a Git tutorial so I won't go over the details. But using version control should be a habit, even if it is just for a tutorial project like this. -[next >>](2-composer.md) +[next >>](02-composer.md) diff --git a/2-composer.md b/02-composer.md similarity index 93% rename from 2-composer.md rename to 02-composer.md index dfb9b00..fca4f45 100644 --- a/2-composer.md +++ b/02-composer.md @@ -1,4 +1,4 @@ -[<< previous](1-front-controller.md) | [next >>](3-error-handler.md) +[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) ### Composer @@ -51,4 +51,4 @@ This will exclude the included file and folder from your commits. For which now Now you have successfully created an empty playground which you can use to set up your project. -[<< previous](1-front-controller.md) | [next >>](3-error-handler.md) +[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) diff --git a/3-error-handler.md b/03-error-handler.md similarity index 96% rename from 3-error-handler.md rename to 03-error-handler.md index baa2358..401762c 100644 --- a/3-error-handler.md +++ b/03-error-handler.md @@ -1,4 +1,4 @@ -[<< previous](2-composer.md) | [next >>](4-http.md) +[<< previous](02-composer.md) | [next >>](04-http.md) ### Error Handler @@ -59,4 +59,4 @@ throw new \Exception; You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until you get it working. Now would also be a good time for another commit. -[<< previous](2-composer.md) | [next >>](4-http.md) +[<< previous](02-composer.md) | [next >>](04-http.md) diff --git a/4-http.md b/04-http.md similarity index 96% rename from 4-http.md rename to 04-http.md index d1ce904..660eece 100644 --- a/4-http.md +++ b/04-http.md @@ -1,4 +1,4 @@ -[<< previous](3-error-handler.md) | [next >>](5-router.md) +[<< previous](03-error-handler.md) | [next >>](05-router.md) ### HTTP @@ -61,4 +61,4 @@ Remember that the object is only storing data, so you if you set multiple status I will show you in later parts how to use the different features of the components. In the meantime, feel free to read the [documentation](https://github.com/PatrickLouys/http) or the source code if you want to find out how something works. -[<< previous](3-error-handler.md) | [next >>](5-router.md) +[<< previous](03-error-handler.md) | [next >>](05-router.md) diff --git a/5-router.md b/05-router.md similarity index 95% rename from 5-router.md rename to 05-router.md index 2bb5c59..a381107 100644 --- a/5-router.md +++ b/05-router.md @@ -1,4 +1,4 @@ -[<< previous](4-http.md) | [next >>](6-dispatching-to-a-class.md) +[<< previous](04-http.md) | [next >>](06-dispatching-to-a-class.md) ### Router @@ -76,4 +76,4 @@ $dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); This is already an improvement, but now all the handler code is in the `Routers.php` file. This is not optimal, so let's fix that in the next part. -[<< previous](4-http.md) | [next >>](6-dispatching-to-a-class.md) +[<< previous](04-http.md) | [next >>](06-dispatching-to-a-class.md) diff --git a/6-dispatching-to-a-class.md b/06-dispatching-to-a-class.md similarity index 94% rename from 6-dispatching-to-a-class.md rename to 06-dispatching-to-a-class.md index 52c96e9..3221f44 100644 --- a/6-dispatching-to-a-class.md +++ b/06-dispatching-to-a-class.md @@ -1,4 +1,4 @@ -[<< previous](5-router.md) | [next >>](7-inversion-of-control.md) +[<< previous](05-router.md) | [next >>](07-inversion-of-control.md) ### Dispatching to a Class @@ -53,4 +53,4 @@ So instead of just calling a method you are now instantiating an object and then Now if you visit `http://localhost:8000/` everything should work. If not, go back and debug. And of course don't forget to commit your changes. -[<< previous](5-router.md) | [next >>](7-inversion-of-control.md) \ No newline at end of file +[<< previous](05-router.md) | [next >>](07-inversion-of-control.md) \ No newline at end of file diff --git a/7-inversion-of-control.md b/07-inversion-of-control.md similarity index 93% rename from 7-inversion-of-control.md rename to 07-inversion-of-control.md index 1192545..2200da5 100644 --- a/7-inversion-of-control.md +++ b/07-inversion-of-control.md @@ -1,4 +1,4 @@ -[<< previous](6-dispatching-to-a-class.md) | [next >>](8-dependency-injector.md) +[<< previous](06-dispatching-to-a-class.md) | [next >>](08-dependency-injector.md) ### Inversion of Control @@ -48,4 +48,4 @@ The `Http\HttpResponse` object implements the `Http\Response` interface, so it f Now everything should work again. But if you follow this example, all your objects that are instantiated this way will have the same objects injected. This is of course not good, so let's fix that in the next part. -[<< previous](6-dispatching-to-a-class.md) | [next >>](8-dependency-injector.md) \ No newline at end of file +[<< previous](06-dispatching-to-a-class.md) | [next >>](08-dependency-injector.md) \ No newline at end of file diff --git a/8-dependency-injector.md b/08-dependency-injector.md similarity index 95% rename from 8-dependency-injector.md rename to 08-dependency-injector.md index f4939af..ffd2a61 100644 --- a/8-dependency-injector.md +++ b/08-dependency-injector.md @@ -1,4 +1,4 @@ -[<< previous](7-inversion-of-control.md) | [next >>](9-templating.md) +[<< previous](07-inversion-of-control.md) | [next >>](09-templating.md) ### Dependency Injector @@ -94,4 +94,4 @@ As you can see now the class has two dependencies. Try to access the page with a Congratulations, you have now successfully laid the groundwork for your application. -[<< previous](7-inversion-of-control.md) | [next >>](9-templating.md) \ No newline at end of file +[<< previous](07-inversion-of-control.md) | [next >>](09-templating.md) \ No newline at end of file diff --git a/9-templating.md b/09-templating.md similarity index 97% rename from 9-templating.md rename to 09-templating.md index 373303f..4165ffe 100644 --- a/9-templating.md +++ b/09-templating.md @@ -1,4 +1,4 @@ -[<< previous](8-dependency-injector.md) | [next >>](10-dynamic-pages.md) +[<< previous](08-dependency-injector.md) | [next >>](10-dynamic-pages.md) ### Templating @@ -136,4 +136,4 @@ Now you can go back to your `Homepage` controller and change the render line to Navigate to the hello page in your browser to make sure everything works. And as always, don't forget to commit your changes. -[<< previous](8-dependency-injector.md) | [next >>](10-dynamic-pages.md) +[<< previous](08-dependency-injector.md) | [next >>](10-dynamic-pages.md) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index 9d2b789..7a9ae70 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -1,4 +1,4 @@ -[<< previous](9-templating.md) +[<< previous](09-templating.md) ### Dynamic Pages @@ -223,4 +223,4 @@ Try a few different URLs to check that everything is working as it should. If so And as always, don't forget to commit your changes. -[<< previous](9-templating.md) \ No newline at end of file +[<< previous](09-templating.md) \ No newline at end of file diff --git a/README.md b/README.md index 03b3de6..3d02a08 100644 --- a/README.md +++ b/README.md @@ -10,17 +10,17 @@ I saw a lot of people coming into the Stack Overflow PHP chatroom and asking if So my goal with this is to provide an easy resource that people can be pointed to. In most cases a framework does not make sense and writing an application from scratch with the help of some third party packages is much, much easier than some people think. -So let's get started right away with the [first part](1-front-controller.md). +So let's get started right away with the [first part](01-front-controller.md). ### Parts -1. [Front Controller](1-front-controller.md) -2. [Composer](2-composer.md) -3. [Error Handler](3-error-handler.md) -4. [HTTP](4-http.md) -5. [Router](5-router.md) -6. [Dispatching to a Class](6-dispatching-to-a-class.md) -7. [Inversion of Control](7-inversion-of-control.md) -8. [Dependency Injector](8-dependency-injector.md) -9. [Templating](9-templating.md) +1. [Front Controller](01-front-controller.md) +2. [Composer](02-composer.md) +3. [Error Handler](03-error-handler.md) +4. [HTTP](04-http.md) +5. [Router](05-router.md) +6. [Dispatching to a Class](06-dispatching-to-a-class.md) +7. [Inversion of Control](07-inversion-of-control.md) +8. [Dependency Injector](08-dependency-injector.md) +9. [Templating](09-templating.md) 10. [Dynamic Pages](10-dynamic-pages.md) From 9294ef7917b7e5c708e98b85dd71d85dd64753dc Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 14 Jan 2015 20:54:18 +0100 Subject: [PATCH 204/314] rename engine to renderer, solves #15 --- 09-templating.md | 28 +++++++++++++++------------- 10-dynamic-pages.md | 14 +++++++------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/09-templating.md b/09-templating.md index 4165ffe..68b895c 100644 --- a/09-templating.md +++ b/09-templating.md @@ -24,20 +24,20 @@ First let's define the interface that we want. Remember the [interface segregati So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a new folder in your `src/` folder with the name `Template` where you can put all the template related things. -In there create a new interface `Engine.php` that looks like this: +In there create a new interface `Renderer.php` that looks like this: ```php alias('Example\Template\Engine', 'Example\Template\MustacheEngineAdapter');` +`$injector->alias('Example\Template\Renderer', 'Example\Template\MustacheRenderer');` Now in your `Homepage` controller, add the new dependency like this: @@ -77,29 +77,27 @@ namespace Example\Controllers; use Http\Request; use Http\Response; -use Example\Template\Engine as TemplateEngine; +use Example\Template\Renderer; class Homepage { private $request; private $response; - private $templateEngine; + private $renderer; public function __construct( Request $request, Response $response, - TemplateEngine $templateEngine + Renderer $renderer ) { $this->request = $request; $this->response = $response; - $this->templateEngine = $templateEngine; + $this->renderer = $renderer; } ... ``` -As you can see I imported the engine with an alias. Without the full namespace it would be relatively unclear what a class does if it is just referenced by `Engine`. Also, another part of the application might also have a class with the name `Engine`. So to avoid that I give it a short and descriptive alias. - We also have to rewrite the `show` method. Please note that while we are just passing in a simple array, Mustache also gives you the option to pass in a view context object. We will go over this later, for now let's keep it as simple as possible. ```php @@ -108,7 +106,7 @@ We also have to rewrite the `show` method. Please note that while we are just pa $data = [ 'name' => $this->request->getParameter('name', 'stranger'), ]; - $html = $this->templateEngine->render('Hello {{name}}', $data); + $html = $this->renderer->render('Hello {{name}}', $data); $this->response->setContent($html); } ``` @@ -132,7 +130,11 @@ In your project root folder, create a `templates` folder. In there, create a fil Hello {{ name }} ``` -Now you can go back to your `Homepage` controller and change the render line to `$content = $this->templateEngine->render('Homepage', $data);` +Now you can go back to your `Homepage` controller and change the render line to `$content = $this->renderer + + + +->render('Homepage', $data);` Navigate to the hello page in your browser to make sure everything works. And as always, don't forget to commit your changes. diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index 7a9ae70..7da2eb5 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -107,12 +107,12 @@ public function show($params) { $slug = $params['slug']; $data['content'] = $this->pageReader->getContentBySlug($slug); - $html = $this->templateEngine->render('Page', $data); + $html = $this->renderer->render('Page', $data); $this->response->setContent($html); } ``` -To make this work, we will need to inject a `Response`, `TemplateEngine` and a `PageReader`. I will leave this to you as an exercise. Remember to `use` all the proper namespaces. Use the `Homepage` controller as a reference. +To make this work, we will need to inject a `Response`, `Renderer` and a `PageReader`. I will leave this to you as an exercise. Remember to `use` all the proper namespaces. Use the `Homepage` controller as a reference. Did you get everything to work? @@ -124,22 +124,22 @@ If not, this is how the beginning of your controller should look now: namespace Example\Controllers; use Http\Response; -use Example\Template\Engine as TemplateEngine; +use Example\Template\Renderer; use Example\Page\PageReader; class Page { private $response; - private $templateEngine; + private $renderer; private $pageReader; public function __construct( Response $response, - TemplateEngine $templateEngine, + Renderer $renderer, PageReader $pageReader ) { $this->response = $response; - $this->templateEngine = $templateEngine; + $this->renderer = $renderer; $this->pageReader = $pageReader; } ... @@ -207,7 +207,7 @@ public function show($params) return $this->response->setContent('404 - Page not found'); } - $html = $this->templateEngine->render('Page', $data); + $html = $this->renderer->render('Page', $data); $this->response->setContent($html); } ``` From 03d9ab097771a55311850dfca01a7caadf0ecdf8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 14 Jan 2015 21:04:27 +0100 Subject: [PATCH 205/314] renamed method to readBySlug. closes #14 --- 10-dynamic-pages.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index 7da2eb5..6e5db14 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -48,7 +48,7 @@ namespace Example\Page; interface PageReader { - public function getContentBySlug($slug); + public function readBySlug($slug); } ``` @@ -73,7 +73,7 @@ class FilePageReader implements PageReader $this->pageFolder = $pageFolder; } - public function getContentBySlug($slug) + public function readBySlug($slug) { return 'I am a placeholder'; } @@ -106,7 +106,7 @@ Now go back to the `Page` controller and change the `show` method to the followi public function show($params) { $slug = $params['slug']; - $data['content'] = $this->pageReader->getContentBySlug($slug); + $data['content'] = $this->pageReader->readBySlug($slug); $html = $this->renderer->render('Page', $data); $this->response->setContent($html); } @@ -150,7 +150,7 @@ So far so good, now let's make our `FilePageReader` actually do some work. Again, let's check first that the proper type was passed into the method: ```php -public function getContentBySlug($slug) +public function readBySlug($slug) { if (!is_string($slug)) { throw new InvalidArgumentException('slug must be a string'); @@ -177,7 +177,7 @@ class InvalidPageException extends Exception } ``` -Then in the `FilePageReader` file add this code at the end of your `getContentBySlug` method: +Then in the `FilePageReader` file add this code at the end of your `readBySlug` method: ``` $path = "$this->pageFolder/$slug.md"; @@ -201,7 +201,7 @@ public function show($params) $slug = $params['slug']; try { - $data['content'] = $this->pageReader->getContentBySlug($slug); + $data['content'] = $this->pageReader->readBySlug($slug); } catch (InvalidPageException $e) { $this->response->setStatusCode(404); return $this->response->setContent('404 - Page not found'); From 4f88cdb0c53088a11d8c4855335b8fdc62cafa73 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 14 Jan 2015 21:09:38 +0100 Subject: [PATCH 206/314] make version requirement more clear. resolves #12 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3d02a08..411e3cb 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ I saw a lot of people coming into the Stack Overflow PHP chatroom and asking if So my goal with this is to provide an easy resource that people can be pointed to. In most cases a framework does not make sense and writing an application from scratch with the help of some third party packages is much, much easier than some people think. +**This tutorial was written with at least PHP 5.5 in mind.** If you are using a lower version, please upgrade it before you start. I recommend that you use the [current stable version](http://php.net/downloads.php). + So let's get started right away with the [first part](01-front-controller.md). ### Parts From 807dbd952746cd2c4370c8be7767752561d67603 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 14 Jan 2015 21:10:28 +0100 Subject: [PATCH 207/314] make version requirement more clear. resolves #12 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 411e3cb..33bc1e5 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ I saw a lot of people coming into the Stack Overflow PHP chatroom and asking if So my goal with this is to provide an easy resource that people can be pointed to. In most cases a framework does not make sense and writing an application from scratch with the help of some third party packages is much, much easier than some people think. -**This tutorial was written with at least PHP 5.5 in mind.** If you are using a lower version, please upgrade it before you start. I recommend that you use the [current stable version](http://php.net/downloads.php). +**This tutorial was written for PHP 5.5 or newer versions.** If you are using a lower version, please upgrade it before you start. I recommend that you use the [current stable version](http://php.net/downloads.php). So let's get started right away with the [first part](01-front-controller.md). From 79bd642cc2a5b7b982546263f8e339bb83e5785c Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 14 Jan 2015 21:10:53 +0100 Subject: [PATCH 208/314] make version requirement more clear. resolves #12 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 33bc1e5..9fe45b2 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ I saw a lot of people coming into the Stack Overflow PHP chatroom and asking if So my goal with this is to provide an easy resource that people can be pointed to. In most cases a framework does not make sense and writing an application from scratch with the help of some third party packages is much, much easier than some people think. -**This tutorial was written for PHP 5.5 or newer versions.** If you are using a lower version, please upgrade it before you start. I recommend that you use the [current stable version](http://php.net/downloads.php). +**This tutorial was written for PHP 5.5 or newer versions.** If you are using an older version, please upgrade it before you start. I recommend that you use the [current stable version](http://php.net/downloads.php). So let's get started right away with the [first part](01-front-controller.md). From c5941658a8ffe0487ca17304b20f9d0797bed1b1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 14 Jan 2015 21:16:39 +0100 Subject: [PATCH 209/314] explain __DIR__. solves #13 --- 01-front-controller.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/01-front-controller.md b/01-front-controller.md index 349c513..75315ea 100644 --- a/01-front-controller.md +++ b/01-front-controller.md @@ -22,6 +22,8 @@ Inside the `public` folder you can now create your `index.php`. Remember that yo require __DIR__ . '/../src/Bootstrap.php'; ``` +`__DIR__` is a [magic constant](http://php.net/manual/en/language.constants.predefined.php) that contains the path of the directory. By using it, you can make sure that the `require` always uses the same relative path to the file it is used in. Otherwise, if you call the `index.php` from a different folder it will not find the file. + The `Bootstrap.php` will be the file that wires your application together. We will get to it shortly. The rest of the public folder is reserved for your public asset files (like JavaScript files and stylesheets). From ded7808ce90a1dedad99948d0651a1a98a68c566 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 21 Jan 2015 23:45:56 +0100 Subject: [PATCH 210/314] begin next chapter --- 10-dynamic-pages.md | 4 ++-- 11-page-menu.md | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 11-page-menu.md diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index 6e5db14..2f9783e 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -1,4 +1,4 @@ -[<< previous](09-templating.md) +[<< previous](09-templating.md) | [next >>](11-page-menu.md) ### Dynamic Pages @@ -223,4 +223,4 @@ Try a few different URLs to check that everything is working as it should. If so And as always, don't forget to commit your changes. -[<< previous](09-templating.md) \ No newline at end of file +[<< previous](09-templating.md) | [next >>](11-page-menu.md) \ No newline at end of file diff --git a/11-page-menu.md b/11-page-menu.md new file mode 100644 index 0000000..6c1f43e --- /dev/null +++ b/11-page-menu.md @@ -0,0 +1,10 @@ +[<< previous](10-dynamic-pages.md) + +### Page Menu + +Now we have some sweet dynamic pages. But nobody can find them. + +Let's fix that now. In this chapter we will create a menu with links to all our pages. + + +[<< previous](10-dynamic-pages.md) \ No newline at end of file From add89f3efe8daf6747dc991c1126d2cdf91ab7de Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Wed, 28 Jan 2015 22:26:47 +0100 Subject: [PATCH 211/314] update header() call to fix overwrite header bug --- 04-http.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/04-http.md b/04-http.md index 660eece..cc33fe3 100644 --- a/04-http.md +++ b/04-http.md @@ -35,7 +35,7 @@ To actually send something back, you will also need to add the following snippet ```php foreach ($response->getHeaders() as $header) { - header($header); + header($header, false); } echo $response->getContent(); @@ -43,6 +43,8 @@ echo $response->getContent(); This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only stores data. This is handled differently by most other HTTP components where the classes send data back to the browser as a side-effect, so keep that in mind if you use another component. +The second parameter of `header()` is false because otherwise existing headers will be overwritten. + Right now it is just sending an empty response back to the browser with the status code `200`; to change that, add the following code between the code snippets from above: ```php From 5970047cbac7b5e912ff65c4980b2ddf9c4fc60e Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 8 Feb 2015 18:00:41 +0100 Subject: [PATCH 212/314] added more to menu chapter --- 09-templating.md | 2 +- 11-page-menu.md | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/09-templating.md b/09-templating.md index 68b895c..f485a09 100644 --- a/09-templating.md +++ b/09-templating.md @@ -8,7 +8,7 @@ A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install that package before you continue. -Other well known alternatives would be [Twig](http://twig.sensiolabs.org/) or [Smarty](http://www.smarty.net/), but they are both pretty bloated and offer too much functionality for just a template engine. +Another well known alternative would be [Twig](http://twig.sensiolabs.org/). Now please go and have a look at the source code of the [engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class does not implement an interface. diff --git a/11-page-menu.md b/11-page-menu.md index 6c1f43e..990496b 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -6,5 +6,26 @@ Now we have some sweet dynamic pages. But nobody can find them. Let's fix that now. In this chapter we will create a menu with links to all our pages. +For a start we will just send a hardcoded array to the template. Go to you `Homepage` controller and change your `$data` array to this: + +```php +$data = [ + 'name' => $this->request->getParameter('name', 'stranger'), + 'menuItems' => ['href' => '/', 'text' => 'Homepage'], +]; +``` + +Now add the following at the top of your `Homepage.mustache` file: + +``` +{{#menuItems}} + {{ text }}
+{{/menuItems}} +``` + +Now if you navigate to your homepage, you should see a link at the top. + +So far so good. But now we realize that we want to reuse this code snippet on every page. We could create a separate file and include it every time, but there is a better solution. + [<< previous](10-dynamic-pages.md) \ No newline at end of file From 4e0743f975989b59c172234e45073df99500290d Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 8 Feb 2015 20:16:44 +0100 Subject: [PATCH 213/314] expanded menu chapter and refactored old chapters --- 09-templating.md | 8 +++-- 10-dynamic-pages.md | 2 +- 11-page-menu.md | 72 ++++++++++++++++++++++++++++++++++++++------- 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/09-templating.md b/09-templating.md index f485a09..d597228 100644 --- a/09-templating.md +++ b/09-templating.md @@ -118,12 +118,16 @@ To make this change we need to pass an options array to the `Mustache_Engine` co ```php $injector->define('Mustache_Engine', [ ':options' => [ - 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__DIR__) . '/templates'), + 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__DIR__) . '/templates', [ + 'extension' => '.html', + ]), ], ]); ``` -In your project root folder, create a `templates` folder. In there, create a file `Homepage.mustache`. The content of the file should look like this: +We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have to rename all the template files. + +In your project root folder, create a `templates` folder. In there, create a file `Homepage.html`. The content of the file should look like this: ```

Hello World

diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index 2f9783e..8565eb7 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -86,7 +86,7 @@ You could also put the page related things into it's own package and reuse it in Because PHP does not have the ability to type hint for scalar values (things like strings and integers), we have to manually check that `$pageFolder` is a string. If we don't do that, there might be a bug in the future that is hard to find if a wrong type is injected. By throwing an exception, this can be caught and debugged immediately. -This will do for now. Let's create a template file for our pages with the name `Page.mustache` in the `templates` folder. For now just add `{{ content }}` in there. +This will do for now. Let's create a template file for our pages with the name `Page.html` in the `templates` folder. For now just add `{{ content }}` in there. Add the following to your `Dependencies.php` file so that the application know which implementation to inject for our new interface. We also define the the `pageFolder` there. diff --git a/11-page-menu.md b/11-page-menu.md index 990496b..2d2ca2e 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -6,26 +6,78 @@ Now we have some sweet dynamic pages. But nobody can find them. Let's fix that now. In this chapter we will create a menu with links to all our pages. -For a start we will just send a hardcoded array to the template. Go to you `Homepage` controller and change your `$data` array to this: +When we have a menu, we will want to be able to reuse the same code on multiple page. We could create a separate file and include it every time, but there is a better solution. + +It is more practical to have templates that are able to extend other templates, like a layout for example. Then we can have all the layout related code in a single file and we don't have to include header and footer files in every template. + +Sadly our implementation of mustache does not support this. We could write code to work around this, which will take time and could introduce some bugs. Or we could switch to a library that already supports this and is well tested. [Twig](http://twig.sensiolabs.org/) for example. + +Now you might wonder why we didn't start with Twig right away. This is a good example to show why using interfaces and writing loosely-coupled code is a good idea. + +Remember how you created a `MustacheRenderer` in [chapter 9](09-templating.md)? This time, we create a `TwigRenderer` that implements the same interface. + +But before we start, install the latest version of Twig with composer. + +Then create the a `TwigRenderer.php` in your `src/Template` folder that looks like this: + +```php +renderer = $renderer; + } + + public function render($template, $data = []) + { + return $this->renderer->render("$template.html", $data); + } +} +``` + +As you can see, on the render function call a `.html` is added. This is because Twig does not add a file ending by default and you would have to specifiy it on every call otherwise. By doing it like this, you can use it in the same way as you used Mustache. + +Add the following code to your `Dependencies.php` file: + +```php +$injector->delegate('Twig_Environment', function() use ($injector) { + $loader = new Twig_Loader_Filesystem(dirname(__DIR__) . '/templates'); + $twig = new Twig_Environment($loader); + return $twig; +}); +``` + +Instead of just defining the dependencies, we are using a delegate to give the responsibility to create the class to a function. This will be useful in the future. + +Now you can switch the `Renderer` alias from `MustacheRenderer` to `TwigRenderer`. Now by default Twig will be used instead of Mustache. + +If you have a look at the site in your browser, everything should work now as before. Now let's get started with the actual menu. + +To start we will just send a hardcoded array to the template. Go to you `Homepage` controller and change your `$data` array to this: ```php $data = [ 'name' => $this->request->getParameter('name', 'stranger'), - 'menuItems' => ['href' => '/', 'text' => 'Homepage'], + 'menuItems' => 'menuItems' => [['href' => '/', 'text' => 'Homepage']], ]; ``` -Now add the following at the top of your `Homepage.mustache` file: +At the top of your `Homepage.html` file add this code: ``` -{{#menuItems}} - {{ text }}
-{{/menuItems}} +{% for item in menuItems %} + {{ item.text }}
+{% endfor %} ``` -Now if you navigate to your homepage, you should see a link at the top. - -So far so good. But now we realize that we want to reuse this code snippet on every page. We could create a separate file and include it every time, but there is a better solution. - +Now if you refresh the homepage in the browser, you should see a link. [<< previous](10-dynamic-pages.md) \ No newline at end of file From 9d654cdd9ec6704ce16e5bfec96e60cbdae76b6a Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 8 Feb 2015 20:51:22 +0100 Subject: [PATCH 214/314] continued menu chapter --- 11-page-menu.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/11-page-menu.md b/11-page-menu.md index 2d2ca2e..48ff549 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -80,4 +80,6 @@ At the top of your `Homepage.html` file add this code: Now if you refresh the homepage in the browser, you should see a link. +to be continued... + [<< previous](10-dynamic-pages.md) \ No newline at end of file From d250548d13a53f50409c5e85c3d39ecb3dfd864e Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 8 Feb 2015 20:53:31 +0100 Subject: [PATCH 215/314] added README entry for new chapter --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9fe45b2..49738f9 100644 --- a/README.md +++ b/README.md @@ -26,3 +26,4 @@ So let's get started right away with the [first part](01-front-controller.md). 8. [Dependency Injector](08-dependency-injector.md) 9. [Templating](09-templating.md) 10. [Dynamic Pages](10-dynamic-pages.md) +11. [Page Menu](11-page-menu.md) From 1b28a9bc1439bb34a3a5941cd955fec841e58ee1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 13 Feb 2015 14:48:12 +0100 Subject: [PATCH 216/314] added layout file --- 11-page-menu.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/11-page-menu.md b/11-page-menu.md index 48ff549..5e1f7e4 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -80,6 +80,44 @@ At the top of your `Homepage.html` file add this code: Now if you refresh the homepage in the browser, you should see a link. +The menu works on the homepage, but we want it on all our pages. We could copy it over to all the template files, but that would be a bad idea. Then if something changes, you would have to go change all the files. + +So instead we are going to use a layout that can be used by all the templates. + +Create a `Layout.html` in your `templates` folder with the following content: + +```php +{% for item in menuItems %} + {{ item['text'] }}
+{% endfor %} +
+{% block content %} +{% endblock %} +``` + +Then change your `Homepage.html` to this: + +```php +{% extends "Layout.html" %} +{% block content %} +

Hello World

+ Hello {{ name }} +{% endblock %} +``` + +And your `Page.html` to this: + +```php +{% extends "Layout.html" %} +{% block content %} + {{ content }} +{% endblock %} +``` + +If you refresh your homepage now, you should see the menu. But if you go to a subpage, the menu is not there but the `
` line is. + +The problem is that we are only passing the `menuItems` to the homepage. Doing that over and over again for all pages would be a bit tedious and a lot of work if something changes. So let's fix that in the next step. + to be continued... [<< previous](10-dynamic-pages.md) \ No newline at end of file From e9569be052e653c72b32369b6883ab23441a4e36 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 13 Feb 2015 16:36:04 +0100 Subject: [PATCH 217/314] frontend renderer --- 11-page-menu.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/11-page-menu.md b/11-page-menu.md index 5e1f7e4..ea0a0b5 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -72,7 +72,7 @@ $data = [ At the top of your `Homepage.html` file add this code: -``` +```php {% for item in menuItems %} {{ item.text }}
{% endfor %} @@ -118,6 +118,64 @@ If you refresh your homepage now, you should see the menu. But if you go to a su The problem is that we are only passing the `menuItems` to the homepage. Doing that over and over again for all pages would be a bit tedious and a lot of work if something changes. So let's fix that in the next step. -to be continued... +We could create a global variable that is usable by all templates, but that is not a good idea here. We will add different parts of the site in the future like an admin area and we will have a different menu there. +So instead we will use a custom renderer for the frontend. First we create an empty interface that extends the existing `Renderer` interface. + +```php +renderer = $renderer; + } + + public function render($template, $data = []) + { + $data = array_merge($data, [ + 'menuItems' => [['href' => '/', 'text' => 'Homepage']], + ]); + return $this->renderer->render($template, $data); + } +} +``` + +As you can see we have a dependency on a `Renderer` in this class. This class is a wrapper for our `Renderer` and adds the `menuItems` to all `$data` arrays. + +Of course we also need to add another alias to the dependencies file. + +```php +$injector->alias('Example\Template\FrontendRenderer', 'Example\Template\FrontendTwigRenderer'); +``` + +Now go to your controllers and exchange all references of `Renderer` with `FrontendRenderer`. Make sure you change it in both the `use` statement at the top and in the constructor. + +Also delete the following line from the `Homepage` controller: + +```php +'menuItems' => [['href' => '/', 'text' => 'Homepage']], +``` + +Once that is done, you should see the menu on both the homepage and your subpages. + +to be continued... [<< previous](10-dynamic-pages.md) \ No newline at end of file From 7feba8e95aeacac092bfefcaf6d8ec5182c2b4cd Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 13 Feb 2015 16:36:27 +0100 Subject: [PATCH 218/314] frontend renderer --- 11-page-menu.md | 1 + 1 file changed, 1 insertion(+) diff --git a/11-page-menu.md b/11-page-menu.md index ea0a0b5..e8baaa4 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -178,4 +178,5 @@ Also delete the following line from the `Homepage` controller: Once that is done, you should see the menu on both the homepage and your subpages. to be continued... + [<< previous](10-dynamic-pages.md) \ No newline at end of file From 8f642f292b7ff20edd271401d0d56cb7e512dff5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 14 Feb 2015 01:07:31 +0100 Subject: [PATCH 219/314] fixed typo --- 11-page-menu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/11-page-menu.md b/11-page-menu.md index e8baaa4..cc33875 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -66,7 +66,7 @@ To start we will just send a hardcoded array to the template. Go to you `Homepag ```php $data = [ 'name' => $this->request->getParameter('name', 'stranger'), - 'menuItems' => 'menuItems' => [['href' => '/', 'text' => 'Homepage']], + 'menuItems' => [['href' => '/', 'text' => 'Homepage']], ]; ``` From 01f07a6f82b58425c52911be5e60cc46c8ede9a7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 2 Sep 2015 19:25:43 +0200 Subject: [PATCH 220/314] finished chapter --- 11-page-menu.md | 80 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/11-page-menu.md b/11-page-menu.md index cc33875..5f8039d 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -175,8 +175,84 @@ Also delete the following line from the `Homepage` controller: 'menuItems' => [['href' => '/', 'text' => 'Homepage']], ``` -Once that is done, you should see the menu on both the homepage and your subpages. +Once that is done, you should see the menu on both the homepage and your subpages. -to be continued... +Everything should work now, but it doesn't really make sense that the menu is defined in the `FrontendTwigRenderer`. So let's refactor that and move it into it's own class. + +Right now the menu is defined in the array, but it is very likely that this will change in the future. Maybe you want to define it in the database or maybe you even want to generate it dynamically based on the pages available. We don't have this information and things might change in the future. + +So let's do the right thing here and start with an interface again. But first, create a new order in the `src` directory for the menu related things. `Menu` sounds like a reasonable name, doesn't it? + +```php + '/', 'text' => 'Homepage']]; + } +} +``` + +This is only a temporary solution to keep things moving forward. We are going to revisit this later. + +Before we continue, let's edit the dependencies file to make sure that our application knows which implementation to use when the interface is requested. + +Add these lines above the `return` statement: + +```php +$injector->alias('Example\Menu\MenuReader', 'Example\Menu\ArrayMenuReader'); +$injector->share('Example\Menu\FileMenuReader'); +``` + +Now you need to change out the hardcoded array in the `FrontendTwigRenderer` class to make it use our new `MenuReader` instead. Give it a try without looking at the solution below. + +Did you finish it or did you get stuck? Or are you just lazy? Doesn't matter, here is a working solution: + +```php +renderer = $renderer; + $this->menuReader = $menuReader; + } + + public function render($template, $data = []) + { + $data = array_merge($data, [ + 'menuItems' => $this->menuReader->readMenu(), + ]); + return $this->renderer->render($template, $data); + } +} +``` + +Everything still working? Awesome. Commit everything and move on to the next chapter. [<< previous](10-dynamic-pages.md) \ No newline at end of file From 767da3ae6aaaf6810a369af4fb9b0bd85fabab4c Mon Sep 17 00:00:00 2001 From: burki94 Date: Mon, 6 Apr 2015 13:12:37 +0200 Subject: [PATCH 221/314] Update 08-dependency-injector.md Auryn\Provider was replaced with Auryn\Injector --- 08-dependency-injector.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/08-dependency-injector.md b/08-dependency-injector.md index ffd2a61..d2dd08e 100644 --- a/08-dependency-injector.md +++ b/08-dependency-injector.md @@ -11,7 +11,7 @@ Install the Auryn package and then create a new file called `Dependencies.php` i ```php alias('Http\Response', 'Http\HttpResponse'); $injector->share('Http\HttpRequest'); @@ -94,4 +94,4 @@ As you can see now the class has two dependencies. Try to access the page with a Congratulations, you have now successfully laid the groundwork for your application. -[<< previous](07-inversion-of-control.md) | [next >>](09-templating.md) \ No newline at end of file +[<< previous](07-inversion-of-control.md) | [next >>](09-templating.md) From 94acccfe81fa6095c2aeefe4422f1dee71aad618 Mon Sep 17 00:00:00 2001 From: Kevin M Granger Date: Thu, 23 Jul 2015 15:09:52 -0700 Subject: [PATCH 222/314] Fix typo in 05-router.md Routers.php -> Routes.php --- 05-router.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/05-router.md b/05-router.md index a381107..d635a58 100644 --- a/05-router.md +++ b/05-router.md @@ -74,6 +74,6 @@ $routeDefinitionCallback = function (\FastRoute\RouteCollector $r) { $dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); ``` -This is already an improvement, but now all the handler code is in the `Routers.php` file. This is not optimal, so let's fix that in the next part. +This is already an improvement, but now all the handler code is in the `Routes.php` file. This is not optimal, so let's fix that in the next part. [<< previous](04-http.md) | [next >>](06-dispatching-to-a-class.md) From 2507095a62fc3a61ac55fbf924b3649c3cafd106 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Sep 2015 21:52:31 +0200 Subject: [PATCH 223/314] frontend --- 11-page-menu.md | 8 +- 12-frontend.md | 188 +++++++++++++++++++++++++++++++++++++++++++++ to-be-continued.md | 14 ++++ 3 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 12-frontend.md create mode 100644 to-be-continued.md diff --git a/11-page-menu.md b/11-page-menu.md index 5f8039d..c46782b 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -1,4 +1,4 @@ -[<< previous](10-dynamic-pages.md) +[<< previous](10-dynamic-pages.md) | [next >>](12-frontend.md) ### Page Menu @@ -205,7 +205,9 @@ class ArrayMenuReader implements MenuReader { public function readMenu() { - return [['href' => '/', 'text' => 'Homepage']]; + return [ + ['href' => '/', 'text' => 'Homepage'], + ]; } } ``` @@ -255,4 +257,4 @@ class FrontendTwigRenderer implements FrontendRenderer Everything still working? Awesome. Commit everything and move on to the next chapter. -[<< previous](10-dynamic-pages.md) \ No newline at end of file +[<< previous](10-dynamic-pages.md) | [next >>](12-frontend.md) \ No newline at end of file diff --git a/12-frontend.md b/12-frontend.md new file mode 100644 index 0000000..620d878 --- /dev/null +++ b/12-frontend.md @@ -0,0 +1,188 @@ +[<< previous](11-page-menu.md) | [next >>](to-be-continued.md) + + +### Frontend + +I don't know about you, but I don't like to work on a site that looks two decades old. So let's improve the look of our little application. + +This is not a frontend tutorial, so we'll just [pure](http://purecss.io/) and call it a day. + +First we need to change the `Layout.html` template. I don't want to waste your time with HTML and CSS, so I'll just provide the code for you to copy paste. + +```php + + + + + Example + + + + +
+ +
+
+ {% block content %} + {% endblock %} +
+
+
+ + +``` + +You will also need some custom CSS. This is a file that we want publicly accessible. So where do we put it? Exactly, in the public folder. + +But to keep things a little organized, add a `css` folder in there first and then create a `style.css` with the following content: + +```css +body { + color: #777; +} + +#layout { + position: relative; + padding-left: 0; +} + +#layout.active #menu { + left: 150px; + width: 150px; +} + +#layout.active .menu-link { + left: 150px; +} + +.content { + margin: 0 auto; + padding: 0 2em; + max-width: 800px; + margin-bottom: 50px; + line-height: 1.6em; +} + +.header { + margin: 0; + color: #333; + text-align: center; + padding: 2.5em 2em 0; + border-bottom: 1px solid #eee; +} + +.header h1 { + margin: 0.2em 0; + font-size: 3em; + font-weight: 300; +} + +.header h2 { + font-weight: 300; + color: #ccc; + padding: 0; + margin-top: 0; +} + +#menu { + margin-left: -150px; + width: 150px; + position: fixed; + top: 0; + left: 0; + bottom: 0; + z-index: 1000; + background: #191818; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + +#menu a { + color: #999; + border: none; + padding: 0.6em 0 0.6em 0.6em; +} + +#menu .pure-menu, +#menu .pure-menu ul { + border: none; + background: transparent; +} + +#menu .pure-menu ul, +#menu .pure-menu .menu-item-divided { + border-top: 1px solid #333; +} + +#menu .pure-menu li a:hover, +#menu .pure-menu li a:focus { + background: #333; +} + +#menu .pure-menu-selected, +#menu .pure-menu-heading { + background: #1f8dd6; +} + +#menu .pure-menu-selected a { + color: #fff; +} + +#menu .pure-menu-heading { + font-size: 110%; + color: #fff; + margin: 0; +} + +.header, +.content { + padding-left: 2em; + padding-right: 2em; +} + +#layout { + padding-left: 150px; /* left col width "#menu" */ + left: 0; +} +#menu { + left: 150px; +} + +.menu-link { + position: fixed; + left: 150px; + display: none; +} + +#layout.active .menu-link { + left: 150px; +} +``` + +Now if you have a look at your site again, things should look a little better. Feel free to further improve the look of it yourself later. But let's continue with the tutorial now. + +Every time that you need an asset or a file publicly available, then you can just put it in your `public` folder. You will need this for all kinds of assets like javascript files, css files, images and more. + +So far so good, but it would be nice if our visitors can see what page they are on. + +Of course we need more than one page in the menu for this. I will just use the `page-one.md` that we created earlier in the tutorial. But feel free to add a few more pages and add them as well. + +Go back to the `ArrayMenuReader` and add your new pages to the array. It should look something like this now: + +```php +return [ + ['href' => '/', 'text' => 'Homepage'], + ['href' => '/page-one', 'text' => 'Page One'], +]; +``` + +[<< previous](11-page-menu.md) | [next >>](to-be-continued.md) + diff --git a/to-be-continued.md b/to-be-continued.md new file mode 100644 index 0000000..b595e24 --- /dev/null +++ b/to-be-continued.md @@ -0,0 +1,14 @@ +### To be continued... + +Congratulations. You made it this far. + +I hope you were following the tutorial step by step and not just skipping over the chapter :) + +I have received good feedback so far so I decided to start writing a book. If you want to learn more and stay updated, [click here](http://artofphp.com/). + +But don't worry, I will also keep working on this tutorial. I was a bit lazy over the summer but now that it getting colder again I will have much more time to spend on the tutorial. + +If you got something out of the tutorial I would appreciate a star. It's the only way for me to see if people are actually reading this :) + +Thanks for your time, +Patrick \ No newline at end of file From 66b3b9ec88a5b91204a26c5c6d3d98177323b1b5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Sep 2015 21:54:13 +0200 Subject: [PATCH 224/314] tbc typo --- to-be-continued.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/to-be-continued.md b/to-be-continued.md index b595e24..598616c 100644 --- a/to-be-continued.md +++ b/to-be-continued.md @@ -2,9 +2,9 @@ Congratulations. You made it this far. -I hope you were following the tutorial step by step and not just skipping over the chapter :) +I hope you were following the tutorial step by step and not just skipping over the chapters :) -I have received good feedback so far so I decided to start writing a book. If you want to learn more and stay updated, [click here](http://artofphp.com/). +I have received good feedback so far so I decided to start writing a book. [Click here](http://artofphp.com/) if you want to learn more or stay updated with how the book is coming along. But don't worry, I will also keep working on this tutorial. I was a bit lazy over the summer but now that it getting colder again I will have much more time to spend on the tutorial. From 256f5272f95a8fb5d5976095f88136f9bcccf56a Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Sep 2015 21:54:53 +0200 Subject: [PATCH 225/314] tbc changed text --- to-be-continued.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/to-be-continued.md b/to-be-continued.md index 598616c..d1e90af 100644 --- a/to-be-continued.md +++ b/to-be-continued.md @@ -4,7 +4,7 @@ Congratulations. You made it this far. I hope you were following the tutorial step by step and not just skipping over the chapters :) -I have received good feedback so far so I decided to start writing a book. [Click here](http://artofphp.com/) if you want to learn more or stay updated with how the book is coming along. +I have received good feedback so far so I decided to start writing a book. [Click here](http://artofphp.com/) to learn more about that. But don't worry, I will also keep working on this tutorial. I was a bit lazy over the summer but now that it getting colder again I will have much more time to spend on the tutorial. From 2c19addde307611e009cd0a5c015e4a79895d62e Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Sep 2015 21:55:29 +0200 Subject: [PATCH 226/314] name on next line --- to-be-continued.md | 1 + 1 file changed, 1 insertion(+) diff --git a/to-be-continued.md b/to-be-continued.md index d1e90af..d485168 100644 --- a/to-be-continued.md +++ b/to-be-continued.md @@ -11,4 +11,5 @@ But don't worry, I will also keep working on this tutorial. I was a bit lazy ove If you got something out of the tutorial I would appreciate a star. It's the only way for me to see if people are actually reading this :) Thanks for your time, + Patrick \ No newline at end of file From c9d86d0ec24a9add63b5a5bc56c5b1c4be968bf0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Sep 2015 21:55:58 +0200 Subject: [PATCH 227/314] spacing --- to-be-continued.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/to-be-continued.md b/to-be-continued.md index d485168..14a1d52 100644 --- a/to-be-continued.md +++ b/to-be-continued.md @@ -10,6 +10,8 @@ But don't worry, I will also keep working on this tutorial. I was a bit lazy ove If you got something out of the tutorial I would appreciate a star. It's the only way for me to see if people are actually reading this :) + + Thanks for your time, Patrick \ No newline at end of file From ba197c6ce67e153d093a2ab7b6189bf47eb5c53b Mon Sep 17 00:00:00 2001 From: Kier Borromeo Date: Tue, 14 Apr 2015 15:54:42 +0800 Subject: [PATCH 228/314] interface to class This seems to be a typo. No? --- 09-templating.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/09-templating.md b/09-templating.md index d597228..f023224 100644 --- a/09-templating.md +++ b/09-templating.md @@ -16,7 +16,7 @@ You could just type hint against the concrete class. But the problem with this a In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want to add functionality to the engine. You can't do that without going back and changing all your code that is tightly coupled. -What we want is loose coupling. We will type hint against an interface that implements an interface. So if you need another implementation, you just implement that interface in your new class and inject the new class instead. +What we want is loose coupling. We will type hint against a class that implements an interface. So if you need another implementation, you just implement that interface in your new class and inject the new class instead. Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). This sounds a lot more complicated than it is, so just follow along. From a1e5fea13d34aa182c084e7995ded98bdb43f267 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 23 Sep 2015 20:23:35 +0200 Subject: [PATCH 229/314] fixed weird sentence --- 09-templating.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/09-templating.md b/09-templating.md index f023224..3e99998 100644 --- a/09-templating.md +++ b/09-templating.md @@ -16,7 +16,7 @@ You could just type hint against the concrete class. But the problem with this a In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want to add functionality to the engine. You can't do that without going back and changing all your code that is tightly coupled. -What we want is loose coupling. We will type hint against a class that implements an interface. So if you need another implementation, you just implement that interface in your new class and inject the new class instead. +What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need another implementation, you just implement that interface in your new class and inject the new class instead. Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). This sounds a lot more complicated than it is, so just follow along. From c117201cec29330f3978098e0eb784f45898dde6 Mon Sep 17 00:00:00 2001 From: Trevor Sawler Date: Thu, 24 Sep 2015 09:07:04 -0300 Subject: [PATCH 230/314] Correct namespace --- 07-inversion-of-control.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/07-inversion-of-control.md b/07-inversion-of-control.md index 2200da5..8a1f5dd 100644 --- a/07-inversion-of-control.md +++ b/07-inversion-of-control.md @@ -13,7 +13,7 @@ Change your `Homepage` controller to the following: ```php Date: Wed, 11 Nov 2015 15:35:46 -0500 Subject: [PATCH 231/314] #35 slightly more logical grouping --- 08-dependency-injector.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/08-dependency-injector.md b/08-dependency-injector.md index d2dd08e..bb0bc83 100644 --- a/08-dependency-injector.md +++ b/08-dependency-injector.md @@ -13,7 +13,7 @@ Install the Auryn package and then create a new file called `Dependencies.php` i $injector = new \Auryn\Injector; -$injector->alias('Http\Response', 'Http\HttpResponse'); +$injector->alias('Http\Request', 'Http\HttpRequest'); $injector->share('Http\HttpRequest'); $injector->define('Http\HttpRequest', [ ':get' => $_GET, @@ -23,7 +23,7 @@ $injector->define('Http\HttpRequest', [ ':server' => $_SERVER, ]); -$injector->alias('Http\Request', 'Http\HttpRequest'); +$injector->alias('Http\Response', 'Http\HttpResponse'); $injector->share('Http\HttpResponse'); return $injector; From 19fa8f37f1f6d5974c938e9d2225ace7c36d95fd Mon Sep 17 00:00:00 2001 From: Hassan Althaf Date: Thu, 26 Nov 2015 11:51:22 +0530 Subject: [PATCH 232/314] Fixed an issue. Fixed the issue stated in: https://github.com/PatrickLouys/no-framework-tutorial/issues/38 --- 09-templating.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/09-templating.md b/09-templating.md index 3e99998..a679aaa 100644 --- a/09-templating.md +++ b/09-templating.md @@ -134,11 +134,7 @@ In your project root folder, create a `templates` folder. In there, create a fil Hello {{ name }} ``` -Now you can go back to your `Homepage` controller and change the render line to `$content = $this->renderer - - - -->render('Homepage', $data);` +Now you can go back to your `Homepage` controller and change the render line to `$html = $this->renderer->render('Homepage', $data);` Navigate to the hello page in your browser to make sure everything works. And as always, don't forget to commit your changes. From 43b9a4cd406b508f2daad1c5314ecd674a5bf6e7 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 29 Nov 2015 19:44:09 +0900 Subject: [PATCH 233/314] Fix code highlight --- 10-dynamic-pages.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index 8565eb7..e2171e8 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -179,7 +179,7 @@ class InvalidPageException extends Exception Then in the `FilePageReader` file add this code at the end of your `readBySlug` method: -``` +```php $path = "$this->pageFolder/$slug.md"; if(!file_exists($path)) { @@ -223,4 +223,4 @@ Try a few different URLs to check that everything is working as it should. If so And as always, don't forget to commit your changes. -[<< previous](09-templating.md) | [next >>](11-page-menu.md) \ No newline at end of file +[<< previous](09-templating.md) | [next >>](11-page-menu.md) From e1fc5e289fdc17f7c5929e434a3092de54564e40 Mon Sep 17 00:00:00 2001 From: Steven Orr Date: Sat, 12 Dec 2015 22:54:35 -0800 Subject: [PATCH 234/314] Replace word 'order' with 'folder'. Author intended to instruct the creation of a new folder called 'Menu'. --- 11-page-menu.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/11-page-menu.md b/11-page-menu.md index c46782b..befdc27 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -181,7 +181,7 @@ Everything should work now, but it doesn't really make sense that the menu is de Right now the menu is defined in the array, but it is very likely that this will change in the future. Maybe you want to define it in the database or maybe you even want to generate it dynamically based on the pages available. We don't have this information and things might change in the future. -So let's do the right thing here and start with an interface again. But first, create a new order in the `src` directory for the menu related things. `Menu` sounds like a reasonable name, doesn't it? +So let's do the right thing here and start with an interface again. But first, create a new folder in the `src` directory for the menu related things. `Menu` sounds like a reasonable name, doesn't it? ```php >](12-frontend.md) \ No newline at end of file +[<< previous](10-dynamic-pages.md) | [next >>](12-frontend.md) From 12e3904fac354a2a920bb7acff80c1e3faf23f90 Mon Sep 17 00:00:00 2001 From: Steven Orr Date: Sat, 12 Dec 2015 23:37:09 -0800 Subject: [PATCH 235/314] Replace 'FileMenuReader' with correct reader. Author intended on sharing 'ArrayMenuReader' not unknown 'FileMenuReader' with injector. --- 11-page-menu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/11-page-menu.md b/11-page-menu.md index befdc27..3e793e3 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -220,7 +220,7 @@ Add these lines above the `return` statement: ```php $injector->alias('Example\Menu\MenuReader', 'Example\Menu\ArrayMenuReader'); -$injector->share('Example\Menu\FileMenuReader'); +$injector->share('Example\Menu\ArrayMenuReader'); ``` Now you need to change out the hardcoded array in the `FrontendTwigRenderer` class to make it use our new `MenuReader` instead. Give it a try without looking at the solution below. From b643bc9f7d51e1b8fbbdb07bc56f8765b3378a56 Mon Sep 17 00:00:00 2001 From: Danack Date: Thu, 17 Dec 2015 02:29:43 +0000 Subject: [PATCH 236/314] Changed lines that said not to commit the lock file. Because not committing it is a bad idea. --- 02-composer.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/02-composer.md b/02-composer.md index fca4f45..47ff793 100644 --- a/02-composer.md +++ b/02-composer.md @@ -40,14 +40,9 @@ In the autoload part you can see that I am using the `Example` namespace for the Open a new console window and navigate into your project root folder. There run `composer update`. -Composer creates a `composer.lock` file that locks in your dependencies and a vendor directory. To remove those from your Git repository, add a new file in your project root folder called `.gitignore` with the following content: +Composer creates a `composer.lock` file that locks in your dependencies and a vendor directory. -```php -composer.lock -vendor/ -``` - -This will exclude the included file and folder from your commits. For which now would be a good time, by the way. +Committing the `composer.lock` file into version control is generally good practice for projects. It allows continuation testing tools (such as [Travis CI](https://travis-ci.org/)) to run the tests against the exact same versions of libraries that you're developing against. It also allows all people who are working on the project to use the exact same version of libraries i.e. it eliminates a source of "works on my machine" problems. Now you have successfully created an empty playground which you can use to set up your project. From f6c08e4cf77c6ef9d206b959f105daaa219041ac Mon Sep 17 00:00:00 2001 From: Gourab Nag Date: Sun, 3 Apr 2016 02:18:02 +0530 Subject: [PATCH 237/314] Fixed Typo in line 38 --- 07-inversion-of-control.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/07-inversion-of-control.md b/07-inversion-of-control.md index 8a1f5dd..5a13293 100644 --- a/07-inversion-of-control.md +++ b/07-inversion-of-control.md @@ -35,7 +35,7 @@ class Homepage Note that you are [importing](http://php.net/manual/en/language.namespaces.importing.php) `Http\Response` at the top of the file. This means that whenever you use `Response` inside this file, it will resolve to the fully qualified name. -In the contructor you are now explicitly asking for a `Http\Response`. In this case, `Http\Response` is an interface. So any class that implements the interface can be injected. See [type hinting](http://php.net/manual/en/language.oop5.typehinting.php) and [interfaces](http://php.net/manual/en/language.oop5.interfaces.php) for reference. +In the constructor you are now explicitly asking for a `Http\Response`. In this case, `Http\Response` is an interface. So any class that implements the interface can be injected. See [type hinting](http://php.net/manual/en/language.oop5.typehinting.php) and [interfaces](http://php.net/manual/en/language.oop5.interfaces.php) for reference. Now the code will result in an error because you are not actually injecting anything. So let's fix that in your `Bootstrap.php` where you dispatch when a route was found: @@ -48,4 +48,4 @@ The `Http\HttpResponse` object implements the `Http\Response` interface, so it f Now everything should work again. But if you follow this example, all your objects that are instantiated this way will have the same objects injected. This is of course not good, so let's fix that in the next part. -[<< previous](06-dispatching-to-a-class.md) | [next >>](08-dependency-injector.md) \ No newline at end of file +[<< previous](06-dispatching-to-a-class.md) | [next >>](08-dependency-injector.md) From 39805a945ee45e0215cdab48528b75da576dc71b Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Wed, 31 Aug 2016 12:51:41 +0200 Subject: [PATCH 238/314] Create LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..edff22d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Patrick Louys + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 6b87bc90d91931f839a52bae18c071fb9d7201b2 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 15:21:21 +0100 Subject: [PATCH 239/314] Updated PHP version requirement --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 49738f9..793f038 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ I saw a lot of people coming into the Stack Overflow PHP chatroom and asking if So my goal with this is to provide an easy resource that people can be pointed to. In most cases a framework does not make sense and writing an application from scratch with the help of some third party packages is much, much easier than some people think. -**This tutorial was written for PHP 5.5 or newer versions.** If you are using an older version, please upgrade it before you start. I recommend that you use the [current stable version](http://php.net/downloads.php). +**This tutorial was written for PHP 7.0 or newer versions.** If you are using an older version, please upgrade it before you start. I recommend that you use the [current stable version](http://php.net/downloads.php). So let's get started right away with the [first part](01-front-controller.md). From cc59e3acd77f87e517691089d23e31a92e7113f8 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 15:33:18 +0100 Subject: [PATCH 240/314] Added strict mode --- 01-front-controller.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/01-front-controller.md b/01-front-controller.md index 75315ea..3032f61 100644 --- a/01-front-controller.md +++ b/01-front-controller.md @@ -17,13 +17,15 @@ So instead of doing that, create a folder in your project folder called `public` Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: ```php - Date: Tue, 1 Nov 2016 15:35:51 +0100 Subject: [PATCH 241/314] Updated PHP version --- 02-composer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/02-composer.md b/02-composer.md index 47ff793..0dd1b3e 100644 --- a/02-composer.md +++ b/02-composer.md @@ -26,7 +26,7 @@ Add the following content to the file: } ], "require": { - "php": ">=5.5.0" + "php": ">=7.0.0" }, "autoload": { "psr-4": { From 9d680095e8c0be3d15ef099c15a22b0657263214 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 15:40:10 +0100 Subject: [PATCH 242/314] Added gitignore --- 01-front-controller.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/01-front-controller.md b/01-front-controller.md index 3032f61..0006f1e 100644 --- a/01-front-controller.md +++ b/01-front-controller.md @@ -44,4 +44,10 @@ If there is an error, go back and try to fix it. If you only see a blank page, c Now would be a good time to commit your progress. If you are not already using Git, set up a repository now. This is not a Git tutorial so I won't go over the details. But using version control should be a habit, even if it is just for a tutorial project like this. +Some editors and IDE's put their own files into your project folders. If that is the case, create a `.gitignore` file in your project root and exclude the files/directories. Below is an example for PHPStorm: + +``` +.idea/ +``` + [next >>](02-composer.md) From 77e685209c95a0daaa2c68467dd7d6fd87de0363 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 15:44:31 +0100 Subject: [PATCH 243/314] Adding vendor folder to gitignore --- 02-composer.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/02-composer.md b/02-composer.md index 0dd1b3e..1771dec 100644 --- a/02-composer.md +++ b/02-composer.md @@ -44,6 +44,12 @@ Composer creates a `composer.lock` file that locks in your dependencies and a ve Committing the `composer.lock` file into version control is generally good practice for projects. It allows continuation testing tools (such as [Travis CI](https://travis-ci.org/)) to run the tests against the exact same versions of libraries that you're developing against. It also allows all people who are working on the project to use the exact same version of libraries i.e. it eliminates a source of "works on my machine" problems. +That being said, [you don't want to put the actual source code of your dependencies in your git repository](https://getcomposer.org/doc/faqs/should-i-commit-the-dependencies-in-my-vendor-directory.md). So let's add a rule to our `.gitignore` file: + +``` +vendor/ +``` + Now you have successfully created an empty playground which you can use to set up your project. [<< previous](01-front-controller.md) | [next >>](03-error-handler.md) From 6ee025c0d3203d95b1717fa71465912dbfa06976 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 15:49:27 +0100 Subject: [PATCH 244/314] Updated composer require --- 03-error-handler.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/03-error-handler.md b/03-error-handler.md index 401762c..4429df6 100644 --- a/03-error-handler.md +++ b/03-error-handler.md @@ -14,8 +14,8 @@ To install a new package, open up your `composer.json` and add the package to th ```php "require": { - "php": ">=5.5.0", - "filp/whoops": ">=1.1.2" + "php": ">=7.0.0", + "filp/whoops": "~2.1" }, ``` From 85f5eb9068ee8f5fa53481291c7d77b39bef4ec9 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 15:52:51 +0100 Subject: [PATCH 245/314] Updated code example --- 03-error-handler.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/03-error-handler.md b/03-error-handler.md index 4429df6..b045e70 100644 --- a/03-error-handler.md +++ b/03-error-handler.md @@ -30,7 +30,7 @@ For development that does not make sense though -- you want a nice error page. T Then after the error handler registration, throw an `Exception` to test if everything is working correctly. Your `Bootstrap.php` should now look similar to this: ```php -pushHandler(new \Whoops\Handler\PrettyPageHandler); } else { $whoops->pushHandler(function($e){ - echo 'Friendly error page and send an email to the developer'; + echo 'Todo: Friendly error page and send an email to the developer'; }); } $whoops->register(); From 4f6e79a6729e7079b92d0c0cf675404d11edc40f Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 15:55:40 +0100 Subject: [PATCH 246/314] Rewrote some sentences --- 04-http.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/04-http.md b/04-http.md index cc33fe3..b245ede 100644 --- a/04-http.md +++ b/04-http.md @@ -4,13 +4,13 @@ PHP already has a few things built in to make working with HTTP easier. For example there are the [superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. -These are good if you just want to get a small script up and running without much thought on maintenance. However, if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, then you will want a class with a nice object-oriented interface that you can use in your application. +These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, then you will want a class with a nice object-oriented interface that you can use in your application instead. Once again, you don't have to reinvent the wheel and just install a package. I decided to write my own [HTTP component](https://github.com/PatrickLouys/http) because I did not like the existing components, but you don't have to do the same. Some alternatives: [Symfony HttpFoundation](https://github.com/symfony/HttpFoundation), [Nette HTTP Component](https://github.com/nette/http), [Aura Web](https://github.com/auraphp/Aura.Web), [sabre/http](https://github.com/fruux/sabre-http) -In this tutorial I will use my own HTTP component, but of course you can use whichever package you like most. Just change the code accordingly. +In this tutorial I will use my own HTTP component, but of course you can use any package that you like. You just have to adapt the code from the tutorial yourself. Again, edit the `composer.json` to add the new component and then run `composer update`: From 3bb8837aaeeac34105e5382b0082d49c0c0573dd Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 15:59:17 +0100 Subject: [PATCH 247/314] Updated composer require --- 04-http.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/04-http.md b/04-http.md index b245ede..09abd12 100644 --- a/04-http.md +++ b/04-http.md @@ -15,11 +15,11 @@ In this tutorial I will use my own HTTP component, but of course you can use any Again, edit the `composer.json` to add the new component and then run `composer update`: ```json -"require": { - "php": ">=5.5.0", - "filp/whoops": ">=1.1.2", - "patricklouys/http": ">=1.1.0" -}, + "require": { + "php": ">=7.0.0", + "filp/whoops": "~2.1", + "patricklouys/http": "~1.4" + }, ``` Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): From 80f382292bf027adc187772bd255d1bc0c543d81 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 16:01:35 +0100 Subject: [PATCH 248/314] Made code location more explicit --- 04-http.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-http.md b/04-http.md index 09abd12..53ed554 100644 --- a/04-http.md +++ b/04-http.md @@ -45,7 +45,7 @@ This will send the response data to the browser. If you don't do this, nothing h The second parameter of `header()` is false because otherwise existing headers will be overwritten. -Right now it is just sending an empty response back to the browser with the status code `200`; to change that, add the following code between the code snippets from above: +Right now it is just sending an empty response back to the browser with the status code `200`; to change that, add the following code between the code snippets from above (just on top of the `foreach` statement): ```php $content = '

Hello World

'; From 1462b9e37cd20177b1858df323e43579b210455a Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 16:18:02 +0100 Subject: [PATCH 249/314] Update 05-router.md --- 05-router.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/05-router.md b/05-router.md index d635a58..fda5b22 100644 --- a/05-router.md +++ b/05-router.md @@ -12,7 +12,7 @@ Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Au By now you know how to install Composer packages, so I will leave that to you. -Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last part. +Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. ```php $dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { @@ -49,7 +49,7 @@ This setup might work for really small applications, but once you start adding a Create a `Routes.php` file in the `src/` folder. It should look like this: ```php ->](06-dispatching-to-a-class.md) From abb4a3b8ba8d4b6126904b5499df754261701248 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 16:23:28 +0100 Subject: [PATCH 250/314] Updated code --- 06-dispatching-to-a-class.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/06-dispatching-to-a-class.md b/06-dispatching-to-a-class.md index 3221f44..7df552a 100644 --- a/06-dispatching-to-a-class.md +++ b/06-dispatching-to-a-class.md @@ -11,7 +11,7 @@ We will need a descriptive name for the classes that handle the requests. For th Create a new folder inside the `src/` folder with the name `Controllers`.In this folder we will place all our controller classes. In there, create a `Homepage.php` file. ```php ->](07-inversion-of-control.md) \ No newline at end of file +[<< previous](05-router.md) | [next >>](07-inversion-of-control.md) From fad56e91db3d7fb9eddc4427999d931071f41c55 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 16:26:05 +0100 Subject: [PATCH 251/314] Improved sentences --- 07-inversion-of-control.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/07-inversion-of-control.md b/07-inversion-of-control.md index 5a13293..a5d82de 100644 --- a/07-inversion-of-control.md +++ b/07-inversion-of-control.md @@ -2,11 +2,11 @@ ### Inversion of Control -In the last part you have set up a controller class and generated output with `echo`. But let's not forget that you have a nice object oriented HTTP abstraction available. But right now it's not accessible inside your class. +In the last part you have set up a controller class and generated output with `echo`. But let's not forget that we have a nice object oriented HTTP abstraction available. But right now it's not accessible inside your class. The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). -If it sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is implemented things will make more sense. +If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is implemented, it will make sense. Change your `Homepage` controller to the following: @@ -33,11 +33,11 @@ class Homepage } ``` -Note that you are [importing](http://php.net/manual/en/language.namespaces.importing.php) `Http\Response` at the top of the file. This means that whenever you use `Response` inside this file, it will resolve to the fully qualified name. +Note that we are [importing](http://php.net/manual/en/language.namespaces.importing.php) `Http\Response` at the top of the file. This means that whenever you use `Response` inside this file, it will resolve to the fully qualified name. -In the constructor you are now explicitly asking for a `Http\Response`. In this case, `Http\Response` is an interface. So any class that implements the interface can be injected. See [type hinting](http://php.net/manual/en/language.oop5.typehinting.php) and [interfaces](http://php.net/manual/en/language.oop5.interfaces.php) for reference. +In the constructor we are now explicitly asking for a `Http\Response`. In this case, `Http\Response` is an interface. So any class that implements the interface can be injected. See [type hinting](http://php.net/manual/en/language.oop5.typehinting.php) and [interfaces](http://php.net/manual/en/language.oop5.interfaces.php) for reference. -Now the code will result in an error because you are not actually injecting anything. So let's fix that in your `Bootstrap.php` where you dispatch when a route was found: +Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: ```php $class = new $className($response); From 052aa890e8ef4e8dc4ca05eecce0189c28cb7af6 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 16:27:27 +0100 Subject: [PATCH 252/314] Update 07-inversion-of-control.md --- 07-inversion-of-control.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/07-inversion-of-control.md b/07-inversion-of-control.md index a5d82de..6149cc4 100644 --- a/07-inversion-of-control.md +++ b/07-inversion-of-control.md @@ -11,7 +11,7 @@ If this sounds a little complicated right now, don't worry. Just follow the tuto Change your `Homepage` controller to the following: ```php - Date: Tue, 1 Nov 2016 16:29:36 +0100 Subject: [PATCH 253/314] Update 08-dependency-injector.md --- 08-dependency-injector.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/08-dependency-injector.md b/08-dependency-injector.md index bb0bc83..d7e583f 100644 --- a/08-dependency-injector.md +++ b/08-dependency-injector.md @@ -4,7 +4,7 @@ A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when the class is instantiated. -There is only one injector that I can recommend: [Auryn](https://github.com/rdlowrey/Auryn). Sadly all the alternatives that I am aware of are using the [service locator antipattern](http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/). +There is only one injector that I can recommend: [Auryn](https://github.com/rdlowrey/Auryn). Sadly all the alternatives that I am aware of are using the [service locator antipattern](http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/) in their documentation and examples. Install the Auryn package and then create a new file called `Dependencies.php` in your `src/` folder. In there add the following content: From 3af2e28c2b2d42173ef365daf67f661fbcd0bc3e Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 16:30:51 +0100 Subject: [PATCH 254/314] added strict mode --- 08-dependency-injector.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/08-dependency-injector.md b/08-dependency-injector.md index d7e583f..4469392 100644 --- a/08-dependency-injector.md +++ b/08-dependency-injector.md @@ -9,7 +9,7 @@ There is only one injector that I can recommend: [Auryn](https://github.com/rdlo Install the Auryn package and then create a new file called `Dependencies.php` in your `src/` folder. In there add the following content: ```php - Date: Tue, 1 Nov 2016 16:48:27 +0100 Subject: [PATCH 255/314] Code changes --- 09-templating.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/09-templating.md b/09-templating.md index a679aaa..1f1bb7c 100644 --- a/09-templating.md +++ b/09-templating.md @@ -27,20 +27,20 @@ So what does our template engine actually need to do? For now we really just nee In there create a new interface `Renderer.php` that looks like this: ```php -engine = $engine; } - public function render($template, $data = []) + public function render($template, $data = []) : string { return $this->engine->render($template, $data); } @@ -71,7 +71,7 @@ Of course we also have to add a definition in our `Dependencies.php` file becaus Now in your `Homepage` controller, add the new dependency like this: ```php - Date: Tue, 1 Nov 2016 16:51:33 +0100 Subject: [PATCH 256/314] Update 09-templating.md --- 09-templating.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/09-templating.md b/09-templating.md index 1f1bb7c..08720de 100644 --- a/09-templating.md +++ b/09-templating.md @@ -6,7 +6,7 @@ A template engine is not necessary with PHP because the language itself can take A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. -For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install that package before you continue. +For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install that package before you continue (`composer require mustache/mustache`). Another well known alternative would be [Twig](http://twig.sensiolabs.org/). From 1b1141999a25f8e3d89cfdbc7491678242dcc1a1 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 16:56:57 +0100 Subject: [PATCH 257/314] Update 10-dynamic-pages.md --- 10-dynamic-pages.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index e2171e8..348e4e3 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -9,7 +9,7 @@ Our first feature will be dynamic pages generated from [markdown](http://en.wiki Create a `Page` controller with the following content: ```php - Date: Tue, 1 Nov 2016 17:00:27 +0100 Subject: [PATCH 258/314] Update 10-dynamic-pages.md --- 10-dynamic-pages.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index 348e4e3..5983b4c 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -42,20 +42,20 @@ So let's put that functionality into a separate class. There is a good chance th In your 'src' folder, create a new folder `Page`. In there we will put all the page related classes. Add a new file in there called `PageReader.php` with this content: ```php -pageFolder = $pageFolder; } - public function readBySlug($slug) + public function readBySlug($slug) : string { return 'I am a placeholder'; } @@ -119,7 +119,7 @@ Did you get everything to work? If not, this is how the beginning of your controller should look now: ```php - Date: Tue, 1 Nov 2016 17:14:02 +0100 Subject: [PATCH 259/314] Update 10-dynamic-pages.md --- 10-dynamic-pages.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index 5983b4c..ca41bb5 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -65,11 +65,8 @@ class FilePageReader implements PageReader { private $pageFolder; - public function __construct($pageFolder) + public function __construct(string $pageFolder) { - if (!is_string($pageFolder)) { - throw new InvalidArgumentException('pageFolder must be a string'); - } $this->pageFolder = $pageFolder; } @@ -84,8 +81,6 @@ As you can see we are requiring the page folder path as a constructor argument. You could also put the page related things into it's own package and reuse it in different applications. Because we are not tightly coupling things, things are very flexible. -Because PHP does not have the ability to type hint for scalar values (things like strings and integers), we have to manually check that `$pageFolder` is a string. If we don't do that, there might be a bug in the future that is hard to find if a wrong type is injected. By throwing an exception, this can be caught and debugged immediately. - This will do for now. Let's create a template file for our pages with the name `Page.html` in the `templates` folder. For now just add `{{ content }}` in there. Add the following to your `Dependencies.php` file so that the application know which implementation to inject for our new interface. We also define the the `pageFolder` there. From 230cbc49f9da2f53598e717c0c08c66e28f54bb9 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 17:17:52 +0100 Subject: [PATCH 260/314] Update 10-dynamic-pages.md --- 10-dynamic-pages.md | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index ca41bb5..eea4ac7 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -48,7 +48,7 @@ namespace Example\Page; interface PageReader { - public function readBySlug($slug) : string; + public function readBySlug(string $slug) : string; } ``` @@ -70,7 +70,7 @@ class FilePageReader implements PageReader $this->pageFolder = $pageFolder; } - public function readBySlug($slug) : string + public function readBySlug(string $slug) : string { return 'I am a placeholder'; } @@ -142,18 +142,7 @@ class Page So far so good, now let's make our `FilePageReader` actually do some work. -Again, let's check first that the proper type was passed into the method: - -```php -public function readBySlug($slug) -{ - if (!is_string($slug)) { - throw new InvalidArgumentException('slug must be a string'); - } -} -``` - -We also need to be able to communicate that a page was not found. For this we can create a custom exception that we can catch later. In your `src/Page` folder, create a `InvalidPageException.php` file with this content: +We need to be able to communicate that a page was not found. For this we can create a custom exception that we can catch later. In your `src/Page` folder, create a `InvalidPageException.php` file with this content: ```php Date: Tue, 1 Nov 2016 17:22:53 +0100 Subject: [PATCH 261/314] Update 10-dynamic-pages.md --- 10-dynamic-pages.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index eea4ac7..d611369 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -166,7 +166,7 @@ Then in the `FilePageReader` file add this code at the end of your `readBySlug` ```php $path = "$this->pageFolder/$slug.md"; -if(!file_exists($path)) { +if (!file_exists($path)) { throw new InvalidPageException($slug); } From be755c597e152bd887d1ac917447b9f7ed48acce Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 17:24:08 +0100 Subject: [PATCH 262/314] Update 10-dynamic-pages.md --- 10-dynamic-pages.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md index d611369..398ba81 100644 --- a/10-dynamic-pages.md +++ b/10-dynamic-pages.md @@ -196,6 +196,8 @@ public function show($params) } ``` +Make sure that you use an `use` statement for the `InvalidPageException` at the top of the file. + Try a few different URLs to check that everything is working as it should. If something is wrong, go back and debug it until it works. And as always, don't forget to commit your changes. From 3f8ca2e1ebba3a9e55f0fabdefe4bbd2c778b107 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 17:52:04 +0100 Subject: [PATCH 263/314] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 793f038..1713dc7 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,4 @@ So let's get started right away with the [first part](01-front-controller.md). 9. [Templating](09-templating.md) 10. [Dynamic Pages](10-dynamic-pages.md) 11. [Page Menu](11-page-menu.md) +12. [Frontend](12-frontend.md) From e4a7ef3b95147d36d820f1a6b87c3194d3917d7a Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Tue, 1 Nov 2016 17:52:22 +0100 Subject: [PATCH 264/314] Update 12-frontend.md --- 12-frontend.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/12-frontend.md b/12-frontend.md index 620d878..bd30564 100644 --- a/12-frontend.md +++ b/12-frontend.md @@ -5,7 +5,7 @@ I don't know about you, but I don't like to work on a site that looks two decades old. So let's improve the look of our little application. -This is not a frontend tutorial, so we'll just [pure](http://purecss.io/) and call it a day. +This is not a frontend tutorial, so we'll just use [pure](http://purecss.io/) and call it a day. First we need to change the `Layout.html` template. I don't want to waste your time with HTML and CSS, so I'll just provide the code for you to copy paste. From b3d0937089e4566d8e6a459e3ac94751c9313f19 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Wed, 2 Nov 2016 13:03:57 +0100 Subject: [PATCH 265/314] Update 11-page-menu.md --- 11-page-menu.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/11-page-menu.md b/11-page-menu.md index 3e793e3..6490067 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -2,17 +2,17 @@ ### Page Menu -Now we have some sweet dynamic pages. But nobody can find them. +Now we have made a few nice dynamic pages. But nobody can find them. -Let's fix that now. In this chapter we will create a menu with links to all our pages. +Let's fix that now. In this chapter we will create a menu with links to all of our pages. -When we have a menu, we will want to be able to reuse the same code on multiple page. We could create a separate file and include it every time, but there is a better solution. +When we have a menu, we will want to be able to reuse the same code on multiple pages. We could create a separate file and include it every time, but there is a better solution. It is more practical to have templates that are able to extend other templates, like a layout for example. Then we can have all the layout related code in a single file and we don't have to include header and footer files in every template. -Sadly our implementation of mustache does not support this. We could write code to work around this, which will take time and could introduce some bugs. Or we could switch to a library that already supports this and is well tested. [Twig](http://twig.sensiolabs.org/) for example. +Our implementation of mustache does not support this. We could write code to work around this, which will take time and could introduce some bugs. Or we could switch to a library that already supports this and is well tested. [Twig](http://twig.sensiolabs.org/) for example. -Now you might wonder why we didn't start with Twig right away. This is a good example to show why using interfaces and writing loosely-coupled code is a good idea. +Now you might wonder why we didn't start with Twig right away. Because this is a good example to show why using interfaces and writing loosely-coupled code is a good idea. Like in the real world, the requirements suddenly changed and now our code needs to adapt. Remember how you created a `MustacheRenderer` in [chapter 9](09-templating.md)? This time, we create a `TwigRenderer` that implements the same interface. From 0e47557468d4169c960f5592e5cd2fe58cf387fe Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Wed, 2 Nov 2016 13:05:02 +0100 Subject: [PATCH 266/314] Update 11-page-menu.md --- 11-page-menu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/11-page-menu.md b/11-page-menu.md index 6490067..6a14fd8 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -16,7 +16,7 @@ Now you might wonder why we didn't start with Twig right away. Because this is a Remember how you created a `MustacheRenderer` in [chapter 9](09-templating.md)? This time, we create a `TwigRenderer` that implements the same interface. -But before we start, install the latest version of Twig with composer. +But before we start, install the latest version of Twig with composer (`composer require "twig/twig:~1.0"`). Then create the a `TwigRenderer.php` in your `src/Template` folder that looks like this: From e34d867cb13f3b79e77177dc7f9238ddeb9db805 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Wed, 2 Nov 2016 13:07:37 +0100 Subject: [PATCH 267/314] Update 11-page-menu.md --- 11-page-menu.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/11-page-menu.md b/11-page-menu.md index 6a14fd8..8c0e4b5 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -21,7 +21,7 @@ But before we start, install the latest version of Twig with composer (`composer Then create the a `TwigRenderer.php` in your `src/Template` folder that looks like this: ```php -renderer = $renderer; } - public function render($template, $data = []) + public function render($template, $data = []) : string { return $this->renderer->render("$template.html", $data); } @@ -123,7 +123,7 @@ We could create a global variable that is usable by all templates, but that is n So instead we will use a custom renderer for the frontend. First we create an empty interface that extends the existing `Renderer` interface. ```php -renderer = $renderer; } - public function render($template, $data = []) + public function render($template, $data = []) : string { $data = array_merge($data, [ 'menuItems' => [['href' => '/', 'text' => 'Homepage']], @@ -184,26 +184,26 @@ Right now the menu is defined in the array, but it is very likely that this will So let's do the right thing here and start with an interface again. But first, create a new folder in the `src` directory for the menu related things. `Menu` sounds like a reasonable name, doesn't it? ```php - '/', 'text' => 'Homepage'], @@ -228,7 +228,7 @@ Now you need to change out the hardcoded array in the `FrontendTwigRenderer` cla Did you finish it or did you get stuck? Or are you just lazy? Doesn't matter, here is a working solution: ```php -menuReader = $menuReader; } - public function render($template, $data = []) + public function render($template, $data = []) : string { $data = array_merge($data, [ 'menuItems' => $this->menuReader->readMenu(), From 633cbf72ecd1da3c95958fda846c618584398e8c Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Wed, 2 Nov 2016 13:14:13 +0100 Subject: [PATCH 268/314] Update 11-page-menu.md --- 11-page-menu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/11-page-menu.md b/11-page-menu.md index 8c0e4b5..d45849e 100644 --- a/11-page-menu.md +++ b/11-page-menu.md @@ -48,7 +48,7 @@ As you can see, on the render function call a `.html` is added. This is because Add the following code to your `Dependencies.php` file: ```php -$injector->delegate('Twig_Environment', function() use ($injector) { +$injector->delegate('Twig_Environment', function () use ($injector) { $loader = new Twig_Loader_Filesystem(dirname(__DIR__) . '/templates'); $twig = new Twig_Environment($loader); return $twig; From 62ad257ccbe5dacb8e26d0a75cf4349f79bd2eb7 Mon Sep 17 00:00:00 2001 From: Michael Skvortsov Date: Thu, 2 Mar 2017 03:51:48 +0200 Subject: [PATCH 269/314] Update 04-http.md A typo fixed --- 04-http.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-http.md b/04-http.md index 53ed554..cf9226d 100644 --- a/04-http.md +++ b/04-http.md @@ -59,7 +59,7 @@ $response->setContent('404 - Page not found'); $response->setStatusCode(404); ``` -Remember that the object is only storing data, so you if you set multiple status codes before you send the response, only the last one will be applied. +Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. I will show you in later parts how to use the different features of the components. In the meantime, feel free to read the [documentation](https://github.com/PatrickLouys/http) or the source code if you want to find out how something works. From 30d22a1cb40b4e620034d4812c0125578a1237b2 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Fri, 8 Dec 2017 20:32:06 +0100 Subject: [PATCH 270/314] Update to-be-continued.md --- to-be-continued.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/to-be-continued.md b/to-be-continued.md index 14a1d52..7207c60 100644 --- a/to-be-continued.md +++ b/to-be-continued.md @@ -4,14 +4,12 @@ Congratulations. You made it this far. I hope you were following the tutorial step by step and not just skipping over the chapters :) -I have received good feedback so far so I decided to start writing a book. [Click here](http://artofphp.com/) to learn more about that. - -But don't worry, I will also keep working on this tutorial. I was a bit lazy over the summer but now that it getting colder again I will have much more time to spend on the tutorial. - If you got something out of the tutorial I would appreciate a star. It's the only way for me to see if people are actually reading this :) +Because this tutorial was so well-received, it inspired me to write a book. The book is a much more up to date version of this tutorial and covers a lot more. Click the link below to check it out (there is also a sample chapter available). +## [Professional PHP: Building maintainable and secure applications](http://patricklouys.com/professional-php/) Thanks for your time, -Patrick \ No newline at end of file +Patrick From c91c0da03214ca23b9206b708ea7cba04c733ba3 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Fri, 8 Dec 2017 20:32:21 +0100 Subject: [PATCH 271/314] Update to-be-continued.md --- to-be-continued.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/to-be-continued.md b/to-be-continued.md index 7207c60..b745fa0 100644 --- a/to-be-continued.md +++ b/to-be-continued.md @@ -8,7 +8,7 @@ If you got something out of the tutorial I would appreciate a star. It's the onl Because this tutorial was so well-received, it inspired me to write a book. The book is a much more up to date version of this tutorial and covers a lot more. Click the link below to check it out (there is also a sample chapter available). -## [Professional PHP: Building maintainable and secure applications](http://patricklouys.com/professional-php/) +### [Professional PHP: Building maintainable and secure applications](http://patricklouys.com/professional-php/) Thanks for your time, From f4d2f6523117ff45cb90ffe6987154049df1dcc4 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Fri, 8 Dec 2017 20:36:52 +0100 Subject: [PATCH 272/314] Update to-be-continued.md --- to-be-continued.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/to-be-continued.md b/to-be-continued.md index b745fa0..a13a0fe 100644 --- a/to-be-continued.md +++ b/to-be-continued.md @@ -10,6 +10,8 @@ Because this tutorial was so well-received, it inspired me to write a book. The ### [Professional PHP: Building maintainable and secure applications](http://patricklouys.com/professional-php/) +![](http://patricklouys.com/img/professional-php-thumb.png) + Thanks for your time, Patrick From 3ef05d6cd7af4c329d47ce11928b79d8a6ce67d0 Mon Sep 17 00:00:00 2001 From: Patrick Louys Date: Fri, 8 Dec 2017 20:39:00 +0100 Subject: [PATCH 273/314] Update README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 1713dc7..ad2da80 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ +Because this tutorial was so well-received, it inspired me to write a book. The book is a much more up to date version of this tutorial and covers a lot more. Click the link below to check it out (there is also a sample chapter available). + +### [Professional PHP: Building maintainable and secure applications](http://patricklouys.com/professional-php/) + +![](http://patricklouys.com/img/professional-php-thumb.png) + +The tutorial is still available in it's original form below. + ## Create a PHP application without a framework ### Introduction From 528ba365b4ba7f60268b6381a47e2d46aee8f5ab Mon Sep 17 00:00:00 2001 From: Stephen Moon Date: Thu, 9 Aug 2018 10:43:52 +0100 Subject: [PATCH 274/314] Small typo --- 05-router.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/05-router.md b/05-router.md index fda5b22..44c4ddb 100644 --- a/05-router.md +++ b/05-router.md @@ -42,7 +42,7 @@ switch ($routeInfo[0]) { } ``` -In the first part of the code, you are registering the available routes for you application. In the second part, the dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, the handler callable will be executed. +In the first part of the code, you are registering the available routes for your application. In the second part, the dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, the handler callable will be executed. This setup might work for really small applications, but once you start adding a few routes your bootstrap file will quickly get cluttered. So let's move them out into a separate file. From 7052803761b1fb55035b7346652f330b3156ed21 Mon Sep 17 00:00:00 2001 From: lubiana Date: Tue, 29 Mar 2022 20:35:06 +0200 Subject: [PATCH 275/314] add data from work folder --- .gitignore | 4 + 01-front-controller.md | 6 +- 02-composer.md | 66 +- 03-error-handler.md | 67 +- 04-development-helpers.md | 260 +++ 04-http.md | 66 - 05-http.md | 124 ++ 05-router.md | 81 - 06-dispatching-to-a-class.md | 56 - 06-router.md | 101 ++ 07-dispatching-to-a-class.md | 137 ++ 07-inversion-of-control.md | 51 - 08-dependency-injector.md | 97 - 08-inversion-of-control.md | 54 + 09-dependency-injector.md | 213 +++ 09-templating.md | 141 -- 10-dynamic-pages.md | 205 --- 10-invoker.md | 102 ++ 11-page-menu.md | 260 --- 11-templating.md | 240 +++ 12-configuration.md | 201 +++ 12-frontend.md | 188 -- 13-refactoring.md | 377 ++++ 14-middleware.md | 220 +++ README.md | 39 +- Vagrantfile | 22 + app/public/favicon.ico | Bin 0 -> 15086 bytes app/src/.gitkeep | 0 app/src/Http/AddRoute.php | 17 + app/src/Http/RouteDecorationMiddleware.php | 97 + app/src/Kernel.php | 63 + .../01-front-controller/public/index.php | 5 + .../01-front-controller/src/Bootstrap.php | 3 + implementation/02-composer/composer.json | 17 + implementation/02-composer/composer.lock | 202 +++ implementation/02-composer/public/index.php | 5 + implementation/02-composer/src/Bootstrap.php | 3 + implementation/03-error-handler/composer.json | 22 + implementation/03-error-handler/composer.lock | 202 +++ .../03-error-handler/public/index.php | 5 + .../03-error-handler/src/Bootstrap.php | 25 + .../04-dev-helpers/.php-cs-fixer.php | 41 + implementation/04-dev-helpers/composer.json | 29 + implementation/04-dev-helpers/composer.lock | 420 +++++ implementation/04-dev-helpers/phpstan.neon | 4 + .../04-dev-helpers/public/index.php | 5 + .../04-dev-helpers/src/Bootstrap.php | 30 + implementation/05-http/.php-cs-fixer.php | 41 + implementation/05-http/composer.json | 30 + implementation/05-http/composer.lock | 627 +++++++ implementation/05-http/phpstan-baseline.neon | 0 implementation/05-http/phpstan.neon | 4 + implementation/05-http/public/index.php | 5 + implementation/05-http/src/Bootstrap.php | 54 + implementation/06-router/.php-cs-fixer.php | 44 + implementation/06-router/composer.json | 31 + implementation/06-router/composer.lock | 677 +++++++ implementation/06-router/config/routes.php | 17 + .../06-router/phpstan-baseline.neon | 0 implementation/06-router/phpstan.neon | 4 + implementation/06-router/public/index.php | 5 + implementation/06-router/src/Bootstrap.php | 89 + .../07-dispatching-to-class/.php-cs-fixer.php | 44 + .../07-dispatching-to-class/composer.json | 32 + .../07-dispatching-to-class/composer.lock | 734 ++++++++ .../07-dispatching-to-class/config/routes.php | 8 + .../phpstan-baseline.neon | 0 .../07-dispatching-to-class/phpstan.neon | 4 + .../07-dispatching-to-class/public/index.php | 5 + .../src/Action/Another.php | 20 + .../src/Action/Hello.php | 21 + .../src/Action/InternalServerError.php | 20 + .../src/Action/NotAllowed.php | 20 + .../src/Action/NotFound.php | 20 + .../07-dispatching-to-class/src/Bootstrap.php | 102 ++ .../InternalServerErrorException.php | 11 + .../src/Exception/NotAllowedException.php | 11 + .../src/Exception/NotFoundException.php | 11 + .../08-inversion-of-control/.php-cs-fixer.php | 44 + .../08-inversion-of-control/composer.json | 32 + .../08-inversion-of-control/composer.lock | 734 ++++++++ .../08-inversion-of-control/config/routes.php | 8 + .../phpstan-baseline.neon | 0 .../08-inversion-of-control/phpstan.neon | 4 + .../08-inversion-of-control/public/index.php | 5 + .../src/Action/Action.php | 17 + .../src/Action/Another.php | 19 + .../src/Action/Hello.php | 20 + .../src/Action/InternalServerError.php | 19 + .../src/Action/NotAllowed.php | 19 + .../src/Action/NotFound.php | 19 + .../08-inversion-of-control/src/Bootstrap.php | 102 ++ .../InternalServerErrorException.php | 11 + .../src/Exception/NotAllowedException.php | 11 + .../src/Exception/NotFoundException.php | 11 + .../09-dependency-injector/.php-cs-fixer.php | 44 + .../09-dependency-injector/composer.json | 34 + .../09-dependency-injector/composer.lock | 1020 +++++++++++ .../config/dependencies.php | 11 + .../09-dependency-injector/config/routes.php | 8 + .../phpstan-baseline.neon | 7 + .../09-dependency-injector/phpstan.neon | 7 + .../09-dependency-injector/public/index.php | 5 + .../src/Action/Action.php | 18 + .../src/Action/Another.php | 19 + .../src/Action/Hello.php | 20 + .../src/Action/InternalServerError.php | 19 + .../src/Action/NotAllowed.php | 19 + .../src/Action/NotFound.php | 19 + .../09-dependency-injector/src/Bootstrap.php | 106 ++ .../InternalServerErrorException.php | 11 + .../src/Exception/NotAllowedException.php | 11 + .../src/Exception/NotFoundException.php | 11 + implementation/10-invoker/.php-cs-fixer.php | 47 + implementation/10-invoker/composer.json | 32 + implementation/10-invoker/composer.lock | 1020 +++++++++++ .../10-invoker/config/dependencies.php | 12 + implementation/10-invoker/config/routes.php | 18 + implementation/10-invoker/phpstan.neon | 5 + implementation/10-invoker/public/favicon.ico | Bin 0 -> 15086 bytes implementation/10-invoker/public/index.php | 6 + implementation/10-invoker/src/.gitkeep | 0 .../10-invoker/src/Action/Hello.php | 23 + .../10-invoker/src/Action/Other.php | 20 + implementation/10-invoker/src/Bootstrap.php | 110 ++ .../src/Exception/InternalServerError.php | 11 + .../src/Exception/MethodNotAllowed.php | 11 + .../10-invoker/src/Exception/NotFound.php | 11 + .../10-invoker/src/Service/Time/Now.php | 12 + .../src/Service/Time/SystemClockNow.php | 15 + .../11-templating/.php-cs-fixer.php | 38 + implementation/11-templating/.phpcs.xml.dist | 9 + implementation/11-templating/composer.json | 46 + implementation/11-templating/composer.lock | 1550 ++++++++++++++++ .../11-templating/config/dependencies.php | 32 + .../11-templating/config/routes.php | 12 + .../11-templating/config/settings.php | 9 + .../11-templating/phpstan-baseline.neon | 7 + implementation/11-templating/phpstan.neon | 8 + .../11-templating/public/favicon.ico | Bin 0 -> 15086 bytes implementation/11-templating/public/index.php | 5 + implementation/11-templating/src/.gitkeep | 0 .../11-templating/src/Action/Hello.php | 31 + .../11-templating/src/Action/Other.php | 19 + .../11-templating/src/Bootstrap.php | 110 ++ .../src/Exception/InternalServerError.php | 9 + .../src/Exception/MethodNotAllowed.php | 9 + .../11-templating/src/Exception/NotFound.php | 9 + .../11-templating/src/Service/Time/Now.php | 10 + .../src/Service/Time/SystemClockNow.php | 13 + implementation/11-templating/src/Settings.php | 13 + .../src/Template/MustacheRenderer.php | 17 + .../11-templating/src/Template/Renderer.php | 11 + .../11-templating/templates/hello.html | 11 + .../12-configuration/.php-cs-fixer.php | 38 + .../12-configuration/.phpcs.xml.dist | 9 + implementation/12-configuration/composer.json | 46 + implementation/12-configuration/composer.lock | 1550 ++++++++++++++++ .../12-configuration/config/dependencies.php | 22 + .../12-configuration/config/routes.php | 12 + .../12-configuration/config/settings.php | 10 + .../12-configuration/phpstan-baseline.neon | 0 implementation/12-configuration/phpstan.neon | 8 + .../12-configuration/public/favicon.ico | Bin 0 -> 15086 bytes .../12-configuration/public/index.php | 5 + implementation/12-configuration/src/.gitkeep | 0 .../12-configuration/src/Action/Hello.php | 31 + .../12-configuration/src/Action/Other.php | 19 + .../12-configuration/src/Bootstrap.php | 111 ++ .../src/Exception/InternalServerError.php | 9 + .../src/Exception/MethodNotAllowed.php | 9 + .../src/Exception/NotFound.php | 9 + .../src/Factory/ContainerProvider.php | 10 + .../Factory/FileSystemSettingsProvider.php | 18 + .../src/Factory/SettingsContainerProvider.php | 25 + .../src/Factory/SettingsProvider.php | 10 + .../12-configuration/src/Service/Time/Now.php | 10 + .../src/Service/Time/SystemClockNow.php | 13 + .../12-configuration/src/Settings.php | 14 + .../src/Template/MustacheRenderer.php | 17 + .../src/Template/Renderer.php | 11 + .../12-configuration/templates/hello.html | 11 + .../13-refactoring/.php-cs-fixer.php | 38 + implementation/13-refactoring/.phpcs.xml.dist | 9 + implementation/13-refactoring/composer.json | 47 + implementation/13-refactoring/composer.lock | 1607 +++++++++++++++++ .../13-refactoring/config/dependencies.php | 39 + .../13-refactoring/config/routes.php | 12 + .../13-refactoring/config/settings.php | 10 + .../13-refactoring/phpstan-baseline.neon | 7 + implementation/13-refactoring/phpstan.neon | 8 + .../13-refactoring/public/favicon.ico | Bin 0 -> 15086 bytes .../13-refactoring/public/index.php | 5 + implementation/13-refactoring/src/.gitkeep | 0 .../13-refactoring/src/Action/Hello.php | 31 + .../13-refactoring/src/Action/Other.php | 19 + .../13-refactoring/src/Bootstrap.php | 40 + .../src/Exception/InternalServerError.php | 9 + .../src/Exception/MethodNotAllowed.php | 9 + .../13-refactoring/src/Exception/NotFound.php | 9 + .../src/Factory/ContainerProvider.php | 10 + .../src/Factory/DiactorosRequestFactory.php | 23 + .../Factory/FileSystemSettingsProvider.php | 18 + .../src/Factory/RequestFactory.php | 11 + .../src/Factory/SettingsContainerProvider.php | 25 + .../src/Factory/SettingsProvider.php | 10 + .../13-refactoring/src/Http/BasicEmitter.php | 38 + .../13-refactoring/src/Http/Emitter.php | 10 + .../src/Http/InvokerRoutedHandler.php | 37 + .../src/Http/RouteMiddleware.php | 69 + .../src/Http/RoutedRequestHandler.php | 10 + implementation/13-refactoring/src/Kernel.php | 34 + .../13-refactoring/src/Service/Time/Now.php | 10 + .../src/Service/Time/SystemClockNow.php | 13 + .../13-refactoring/src/Settings.php | 14 + .../src/Template/MustacheRenderer.php | 17 + .../13-refactoring/src/Template/Renderer.php | 11 + .../13-refactoring/templates/hello.html | 11 + 218 files changed, 16123 insertions(+), 1233 deletions(-) create mode 100644 .gitignore create mode 100644 04-development-helpers.md delete mode 100644 04-http.md create mode 100644 05-http.md delete mode 100644 05-router.md delete mode 100644 06-dispatching-to-a-class.md create mode 100644 06-router.md create mode 100644 07-dispatching-to-a-class.md delete mode 100644 07-inversion-of-control.md delete mode 100644 08-dependency-injector.md create mode 100644 08-inversion-of-control.md create mode 100644 09-dependency-injector.md delete mode 100644 09-templating.md delete mode 100644 10-dynamic-pages.md create mode 100644 10-invoker.md delete mode 100644 11-page-menu.md create mode 100644 11-templating.md create mode 100644 12-configuration.md delete mode 100644 12-frontend.md create mode 100644 13-refactoring.md create mode 100644 14-middleware.md create mode 100644 Vagrantfile create mode 100644 app/public/favicon.ico create mode 100644 app/src/.gitkeep create mode 100644 app/src/Http/AddRoute.php create mode 100644 app/src/Http/RouteDecorationMiddleware.php create mode 100644 app/src/Kernel.php create mode 100644 implementation/01-front-controller/public/index.php create mode 100644 implementation/01-front-controller/src/Bootstrap.php create mode 100644 implementation/02-composer/composer.json create mode 100644 implementation/02-composer/composer.lock create mode 100644 implementation/02-composer/public/index.php create mode 100644 implementation/02-composer/src/Bootstrap.php create mode 100644 implementation/03-error-handler/composer.json create mode 100644 implementation/03-error-handler/composer.lock create mode 100644 implementation/03-error-handler/public/index.php create mode 100644 implementation/03-error-handler/src/Bootstrap.php create mode 100644 implementation/04-dev-helpers/.php-cs-fixer.php create mode 100644 implementation/04-dev-helpers/composer.json create mode 100644 implementation/04-dev-helpers/composer.lock create mode 100644 implementation/04-dev-helpers/phpstan.neon create mode 100644 implementation/04-dev-helpers/public/index.php create mode 100644 implementation/04-dev-helpers/src/Bootstrap.php create mode 100644 implementation/05-http/.php-cs-fixer.php create mode 100644 implementation/05-http/composer.json create mode 100644 implementation/05-http/composer.lock create mode 100644 implementation/05-http/phpstan-baseline.neon create mode 100644 implementation/05-http/phpstan.neon create mode 100644 implementation/05-http/public/index.php create mode 100644 implementation/05-http/src/Bootstrap.php create mode 100644 implementation/06-router/.php-cs-fixer.php create mode 100644 implementation/06-router/composer.json create mode 100644 implementation/06-router/composer.lock create mode 100644 implementation/06-router/config/routes.php create mode 100644 implementation/06-router/phpstan-baseline.neon create mode 100644 implementation/06-router/phpstan.neon create mode 100644 implementation/06-router/public/index.php create mode 100644 implementation/06-router/src/Bootstrap.php create mode 100644 implementation/07-dispatching-to-class/.php-cs-fixer.php create mode 100644 implementation/07-dispatching-to-class/composer.json create mode 100644 implementation/07-dispatching-to-class/composer.lock create mode 100644 implementation/07-dispatching-to-class/config/routes.php create mode 100644 implementation/07-dispatching-to-class/phpstan-baseline.neon create mode 100644 implementation/07-dispatching-to-class/phpstan.neon create mode 100644 implementation/07-dispatching-to-class/public/index.php create mode 100644 implementation/07-dispatching-to-class/src/Action/Another.php create mode 100644 implementation/07-dispatching-to-class/src/Action/Hello.php create mode 100644 implementation/07-dispatching-to-class/src/Action/InternalServerError.php create mode 100644 implementation/07-dispatching-to-class/src/Action/NotAllowed.php create mode 100644 implementation/07-dispatching-to-class/src/Action/NotFound.php create mode 100644 implementation/07-dispatching-to-class/src/Bootstrap.php create mode 100644 implementation/07-dispatching-to-class/src/Exception/InternalServerErrorException.php create mode 100644 implementation/07-dispatching-to-class/src/Exception/NotAllowedException.php create mode 100644 implementation/07-dispatching-to-class/src/Exception/NotFoundException.php create mode 100644 implementation/08-inversion-of-control/.php-cs-fixer.php create mode 100644 implementation/08-inversion-of-control/composer.json create mode 100644 implementation/08-inversion-of-control/composer.lock create mode 100644 implementation/08-inversion-of-control/config/routes.php create mode 100644 implementation/08-inversion-of-control/phpstan-baseline.neon create mode 100644 implementation/08-inversion-of-control/phpstan.neon create mode 100644 implementation/08-inversion-of-control/public/index.php create mode 100644 implementation/08-inversion-of-control/src/Action/Action.php create mode 100644 implementation/08-inversion-of-control/src/Action/Another.php create mode 100644 implementation/08-inversion-of-control/src/Action/Hello.php create mode 100644 implementation/08-inversion-of-control/src/Action/InternalServerError.php create mode 100644 implementation/08-inversion-of-control/src/Action/NotAllowed.php create mode 100644 implementation/08-inversion-of-control/src/Action/NotFound.php create mode 100644 implementation/08-inversion-of-control/src/Bootstrap.php create mode 100644 implementation/08-inversion-of-control/src/Exception/InternalServerErrorException.php create mode 100644 implementation/08-inversion-of-control/src/Exception/NotAllowedException.php create mode 100644 implementation/08-inversion-of-control/src/Exception/NotFoundException.php create mode 100644 implementation/09-dependency-injector/.php-cs-fixer.php create mode 100644 implementation/09-dependency-injector/composer.json create mode 100644 implementation/09-dependency-injector/composer.lock create mode 100644 implementation/09-dependency-injector/config/dependencies.php create mode 100644 implementation/09-dependency-injector/config/routes.php create mode 100644 implementation/09-dependency-injector/phpstan-baseline.neon create mode 100644 implementation/09-dependency-injector/phpstan.neon create mode 100644 implementation/09-dependency-injector/public/index.php create mode 100644 implementation/09-dependency-injector/src/Action/Action.php create mode 100644 implementation/09-dependency-injector/src/Action/Another.php create mode 100644 implementation/09-dependency-injector/src/Action/Hello.php create mode 100644 implementation/09-dependency-injector/src/Action/InternalServerError.php create mode 100644 implementation/09-dependency-injector/src/Action/NotAllowed.php create mode 100644 implementation/09-dependency-injector/src/Action/NotFound.php create mode 100644 implementation/09-dependency-injector/src/Bootstrap.php create mode 100644 implementation/09-dependency-injector/src/Exception/InternalServerErrorException.php create mode 100644 implementation/09-dependency-injector/src/Exception/NotAllowedException.php create mode 100644 implementation/09-dependency-injector/src/Exception/NotFoundException.php create mode 100644 implementation/10-invoker/.php-cs-fixer.php create mode 100644 implementation/10-invoker/composer.json create mode 100644 implementation/10-invoker/composer.lock create mode 100644 implementation/10-invoker/config/dependencies.php create mode 100644 implementation/10-invoker/config/routes.php create mode 100644 implementation/10-invoker/phpstan.neon create mode 100644 implementation/10-invoker/public/favicon.ico create mode 100644 implementation/10-invoker/public/index.php create mode 100644 implementation/10-invoker/src/.gitkeep create mode 100644 implementation/10-invoker/src/Action/Hello.php create mode 100644 implementation/10-invoker/src/Action/Other.php create mode 100644 implementation/10-invoker/src/Bootstrap.php create mode 100644 implementation/10-invoker/src/Exception/InternalServerError.php create mode 100644 implementation/10-invoker/src/Exception/MethodNotAllowed.php create mode 100644 implementation/10-invoker/src/Exception/NotFound.php create mode 100644 implementation/10-invoker/src/Service/Time/Now.php create mode 100644 implementation/10-invoker/src/Service/Time/SystemClockNow.php create mode 100644 implementation/11-templating/.php-cs-fixer.php create mode 100644 implementation/11-templating/.phpcs.xml.dist create mode 100644 implementation/11-templating/composer.json create mode 100644 implementation/11-templating/composer.lock create mode 100644 implementation/11-templating/config/dependencies.php create mode 100644 implementation/11-templating/config/routes.php create mode 100644 implementation/11-templating/config/settings.php create mode 100644 implementation/11-templating/phpstan-baseline.neon create mode 100644 implementation/11-templating/phpstan.neon create mode 100644 implementation/11-templating/public/favicon.ico create mode 100644 implementation/11-templating/public/index.php create mode 100644 implementation/11-templating/src/.gitkeep create mode 100644 implementation/11-templating/src/Action/Hello.php create mode 100644 implementation/11-templating/src/Action/Other.php create mode 100644 implementation/11-templating/src/Bootstrap.php create mode 100644 implementation/11-templating/src/Exception/InternalServerError.php create mode 100644 implementation/11-templating/src/Exception/MethodNotAllowed.php create mode 100644 implementation/11-templating/src/Exception/NotFound.php create mode 100644 implementation/11-templating/src/Service/Time/Now.php create mode 100644 implementation/11-templating/src/Service/Time/SystemClockNow.php create mode 100644 implementation/11-templating/src/Settings.php create mode 100644 implementation/11-templating/src/Template/MustacheRenderer.php create mode 100644 implementation/11-templating/src/Template/Renderer.php create mode 100644 implementation/11-templating/templates/hello.html create mode 100644 implementation/12-configuration/.php-cs-fixer.php create mode 100644 implementation/12-configuration/.phpcs.xml.dist create mode 100644 implementation/12-configuration/composer.json create mode 100644 implementation/12-configuration/composer.lock create mode 100644 implementation/12-configuration/config/dependencies.php create mode 100644 implementation/12-configuration/config/routes.php create mode 100644 implementation/12-configuration/config/settings.php create mode 100644 implementation/12-configuration/phpstan-baseline.neon create mode 100644 implementation/12-configuration/phpstan.neon create mode 100644 implementation/12-configuration/public/favicon.ico create mode 100644 implementation/12-configuration/public/index.php create mode 100644 implementation/12-configuration/src/.gitkeep create mode 100644 implementation/12-configuration/src/Action/Hello.php create mode 100644 implementation/12-configuration/src/Action/Other.php create mode 100644 implementation/12-configuration/src/Bootstrap.php create mode 100644 implementation/12-configuration/src/Exception/InternalServerError.php create mode 100644 implementation/12-configuration/src/Exception/MethodNotAllowed.php create mode 100644 implementation/12-configuration/src/Exception/NotFound.php create mode 100644 implementation/12-configuration/src/Factory/ContainerProvider.php create mode 100644 implementation/12-configuration/src/Factory/FileSystemSettingsProvider.php create mode 100644 implementation/12-configuration/src/Factory/SettingsContainerProvider.php create mode 100644 implementation/12-configuration/src/Factory/SettingsProvider.php create mode 100644 implementation/12-configuration/src/Service/Time/Now.php create mode 100644 implementation/12-configuration/src/Service/Time/SystemClockNow.php create mode 100644 implementation/12-configuration/src/Settings.php create mode 100644 implementation/12-configuration/src/Template/MustacheRenderer.php create mode 100644 implementation/12-configuration/src/Template/Renderer.php create mode 100644 implementation/12-configuration/templates/hello.html create mode 100644 implementation/13-refactoring/.php-cs-fixer.php create mode 100644 implementation/13-refactoring/.phpcs.xml.dist create mode 100644 implementation/13-refactoring/composer.json create mode 100644 implementation/13-refactoring/composer.lock create mode 100644 implementation/13-refactoring/config/dependencies.php create mode 100644 implementation/13-refactoring/config/routes.php create mode 100644 implementation/13-refactoring/config/settings.php create mode 100644 implementation/13-refactoring/phpstan-baseline.neon create mode 100644 implementation/13-refactoring/phpstan.neon create mode 100644 implementation/13-refactoring/public/favicon.ico create mode 100644 implementation/13-refactoring/public/index.php create mode 100644 implementation/13-refactoring/src/.gitkeep create mode 100644 implementation/13-refactoring/src/Action/Hello.php create mode 100644 implementation/13-refactoring/src/Action/Other.php create mode 100644 implementation/13-refactoring/src/Bootstrap.php create mode 100644 implementation/13-refactoring/src/Exception/InternalServerError.php create mode 100644 implementation/13-refactoring/src/Exception/MethodNotAllowed.php create mode 100644 implementation/13-refactoring/src/Exception/NotFound.php create mode 100644 implementation/13-refactoring/src/Factory/ContainerProvider.php create mode 100644 implementation/13-refactoring/src/Factory/DiactorosRequestFactory.php create mode 100644 implementation/13-refactoring/src/Factory/FileSystemSettingsProvider.php create mode 100644 implementation/13-refactoring/src/Factory/RequestFactory.php create mode 100644 implementation/13-refactoring/src/Factory/SettingsContainerProvider.php create mode 100644 implementation/13-refactoring/src/Factory/SettingsProvider.php create mode 100644 implementation/13-refactoring/src/Http/BasicEmitter.php create mode 100644 implementation/13-refactoring/src/Http/Emitter.php create mode 100644 implementation/13-refactoring/src/Http/InvokerRoutedHandler.php create mode 100644 implementation/13-refactoring/src/Http/RouteMiddleware.php create mode 100644 implementation/13-refactoring/src/Http/RoutedRequestHandler.php create mode 100644 implementation/13-refactoring/src/Kernel.php create mode 100644 implementation/13-refactoring/src/Service/Time/Now.php create mode 100644 implementation/13-refactoring/src/Service/Time/SystemClockNow.php create mode 100644 implementation/13-refactoring/src/Settings.php create mode 100644 implementation/13-refactoring/src/Template/MustacheRenderer.php create mode 100644 implementation/13-refactoring/src/Template/Renderer.php create mode 100644 implementation/13-refactoring/templates/hello.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cc205bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +**/vendor/ +**/.php-cs-fixer.cache +.idea/ +.vagrant/ \ No newline at end of file diff --git a/01-front-controller.md b/01-front-controller.md index 0006f1e..87a12ad 100644 --- a/01-front-controller.md +++ b/01-front-controller.md @@ -17,7 +17,7 @@ So instead of doing that, create a folder in your project folder called `public` Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: ```php -=7.0.0" - }, - "autoload": { - "psr-4": { - "Example\\": "src/" - } + "name": "lubian/no-framework", + "require": { + "php": "^8.1" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" } + }, + "authors": [ + { + "name": "lubiana", + "email": "lubiana@hannover.ccc.de" + } + ] } ``` -In the autoload part you can see that I am using the `Example` namespace for the project. You can use whatever fits your project there, but from now on I will always use the `Example` namespace in my examples. Just replace it with your namespace in your own code. +In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use +whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. +Just replace it with your namespace in your own code. + +I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. + +As the Bootstrap.php file is placed in that directory we should +add the namespace to the File as well. Here is my current Bootstrap.php +as a reference: + +```php +>](04-http.md) +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) ### Error Handler An error handler allows you to customize what happens if your code results in an error. -A nice error page with a lot of information for debugging goes a long way during development. So the first package for your application will take care of that. +A nice error page with a lot of information for debugging goes a long way during development. So the first package +for your application will take care of that. -I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, you have total control over your project. +I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. +If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, +you have total control over your project. An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) -To install a new package, open up your `composer.json` and add the package to the require part. It should now look like this: +To install a new package, open up your `composer.json` and add the package to the require part. It should now look +like this: ```php "require": { - "php": ">=7.0.0", - "filp/whoops": "~2.1" + "php": ">=8.1.0", + "filp/whoops": "^2.14" }, ``` Now run `composer update` in your console and it will be installed. -But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. +Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, +i that case composer automatically installs the package and updates your composer.json-file. -**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message can help someone to gain access to your system. Always show a user friendly error page instead and send an email to yourself, write to a log or something similar. So only you can see the errors in the production environment. +But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, +ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you +only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. -For development that does not make sense though -- you want a nice error page. The solution is to have an environment switch in your code. For now you can just set it to `development`. +**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message +can help someone to gain access to your system. Always show a user friendly error page instead and send an email to +yourself, write to a log or something similar. So only you can see the errors in the production environment. -Then after the error handler registration, throw an `Exception` to test if everything is working correctly. Your `Bootstrap.php` should now look similar to this: +For development that does not make sense though -- you want a nice error page. The solution is to have an environment +switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in +case no environment has been set. + +Then after the error handler registration, throw an `Exception` to test if everything is working correctly. +Your `Bootstrap.php` should now look similar to this: ```php -pushHandler(new \Whoops\Handler\PrettyPageHandler); +$whoops = new Run; +if ($environment == 'dev') { + $whoops->pushHandler(new PrettyPageHandler); } else { - $whoops->pushHandler(function($e){ - echo 'Todo: Friendly error page and send an email to the developer'; + $whoops->pushHandler(function (\Throwable $e) { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; }); } $whoops->register(); -throw new \Exception; +throw new \Exception("Ooooopsie"); ``` -You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until you get it working. Now would also be a good time for another commit. +You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until +you get it working. Now would also be a good time for another commit. -[<< previous](02-composer.md) | [next >>](04-http.md) + +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/04-development-helpers.md b/04-development-helpers.md new file mode 100644 index 0000000..74f913c --- /dev/null +++ b/04-development-helpers.md @@ -0,0 +1,260 @@ +[<< previous](03-error-handler.md) | [next >>](05-http.md) + +### Development Helpers + +I have added some more helpers to my composer.json that help me with development. As these are scripts and programms +used only for development they should not be used in a production environment. Composer has a specific sections in its +file called "dev-dependencies", everything that is required in this section does not get installen in production. + +Let's install our dev-helpers and i will explain them one by one: +`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` + +#### Static Code Analysis with phpstan + +Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or +create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements +that are not (or not always) available, if-statements that always are true and a lot of other stuff. + +A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. + +```php +/** + * @param \DateTime $date + * @return void + */ +function printDate($date) { + $date->format('Y-m-d H:i:s'); +} + +printDate('now'); +``` +if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` + +It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has +no use, and secondly, that we are calling the function with a string instead of a datetime object. + +```shell +1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + ------ --------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ --------------------------------------------------------------------------------------------- +30 Call to method DateTime::format() on a separate line has no effect. +33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. + ------ --------------------------------------------------------------------------------------------- +``` + +The second error is something that "declare strict-types" already catches for us, but the first error is something that +we usually would not discover easily without speccially looking for this errortype. + +We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and +path everytime we want to check our code for errors: + +```yaml +parameters: + level: max + paths: + - src +``` +now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project + +With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us +some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. + +```shell +composer require --dev phpstan/extension-installer +composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules +``` + +During the first install you need to allow the extension installer to actually install the extension. The second command +installs some more strict rulesets and activates them in phpstan. + +If we now rerun phpstan it already tells us about some errors we have made: + +``` + ------ ----------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ ----------------------------------------------------------------------------------------------- +10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider + using long ternary. +25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: + http://bit.ly/subtypeexception +26 Unreachable statement - code above always terminates. + ------ ----------------------------------------------------------------------------------------------- +``` + +The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove +that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific +problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working +on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages +everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we +are adding to our code. + +In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the +phpstan.neon file and run phpstan with the +'--generate-baseline' option: + +```yaml +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` +```shell +[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline +Note: Using configuration file /home/vagrant/app/phpstan.neon. + 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + + + [OK] Baseline generated with 1 error. + + +``` + +you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) + +#### PHP-CS-Fixer + +Another great tool is the php-cs-fixer, which just applies a specific style to your code. + +when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current +directory. + +You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) + +personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup +a configuration file that i use in all my projects .php-cs-fixer.php: + +```php +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + ]) + ); +``` + +#### PHP Codesniffer + +The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra +rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. + +it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. + +Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have +guessed we are adding some extra rules. + +Lets install the ruleset with composer +```shell +composer require --dev mnapoli/hard-mode +``` + +and add a configuration file to actually use it '.phpcs.xml.dist' +```xml + + + + + src + + + +``` + +running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. + +``` +[vagrant@archlinux app]$ ./vendor/bin/phpcs + +FILE: src/Bootstrap.php +---------------------------------------------------------------------------------------------------- +FOUND 4 ERRORS AFFECTING 4 LINES +---------------------------------------------------------------------------------------------------- + 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. + 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead +---------------------------------------------------------------------------------------------------- +PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY +---------------------------------------------------------------------------------------------------- + +Time: 639ms; Memory: 10MB +``` + +You can then use `./vendor/bin/phpcbf` to try to fix them + + +#### Symfony Var-Dumper + +another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small +functions. + +dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects +or arrays. + +dd() on the other hand is a function that dumps its parameters and then exits the php-script. + +you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. + +#### Composer scripts + +now we have a few commands that are available on the command line. i personally do not like to type complex commands +with lots of parameters by hand all the time, so i added a few lines to my composer.json: + +```json +"scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" +}, +``` + +that way i can just type "composer" followed by the command name in the root of my project. if i want to start the +php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the +time. + +You could also configure PhpStorm to automatically run these commands in the background and highlight the violations +directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my +flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. + +My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before +commiting and pushing the code. + +[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/04-http.md b/04-http.md deleted file mode 100644 index cf9226d..0000000 --- a/04-http.md +++ /dev/null @@ -1,66 +0,0 @@ -[<< previous](03-error-handler.md) | [next >>](05-router.md) - -### HTTP - -PHP already has a few things built in to make working with HTTP easier. For example there are the [superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. - -These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, then you will want a class with a nice object-oriented interface that you can use in your application instead. - -Once again, you don't have to reinvent the wheel and just install a package. I decided to write my own [HTTP component](https://github.com/PatrickLouys/http) because I did not like the existing components, but you don't have to do the same. - -Some alternatives: [Symfony HttpFoundation](https://github.com/symfony/HttpFoundation), [Nette HTTP Component](https://github.com/nette/http), [Aura Web](https://github.com/auraphp/Aura.Web), [sabre/http](https://github.com/fruux/sabre-http) - -In this tutorial I will use my own HTTP component, but of course you can use any package that you like. You just have to adapt the code from the tutorial yourself. - -Again, edit the `composer.json` to add the new component and then run `composer update`: - -```json - "require": { - "php": ">=7.0.0", - "filp/whoops": "~2.1", - "patricklouys/http": "~1.4" - }, -``` - -Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): - -```php -$request = new \Http\HttpRequest($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER); -$response = new \Http\HttpResponse; -``` - -This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. - -To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: - -```php -foreach ($response->getHeaders() as $header) { - header($header, false); -} - -echo $response->getContent(); -``` - -This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only stores data. This is handled differently by most other HTTP components where the classes send data back to the browser as a side-effect, so keep that in mind if you use another component. - -The second parameter of `header()` is false because otherwise existing headers will be overwritten. - -Right now it is just sending an empty response back to the browser with the status code `200`; to change that, add the following code between the code snippets from above (just on top of the `foreach` statement): - -```php -$content = '

Hello World

'; -$response->setContent($content); -``` - -If you want to try a 404 error, use the following code: - -```php -$response->setContent('404 - Page not found'); -$response->setStatusCode(404); -``` - -Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. - -I will show you in later parts how to use the different features of the components. In the meantime, feel free to read the [documentation](https://github.com/PatrickLouys/http) or the source code if you want to find out how something works. - -[<< previous](03-error-handler.md) | [next >>](05-router.md) diff --git a/05-http.md b/05-http.md new file mode 100644 index 0000000..6166214 --- /dev/null +++ b/05-http.md @@ -0,0 +1,124 @@ +[<< previous](04-development-helpers.md) | [next >>](06-router.md) + +### HTTP + +PHP already has a few things built in to make working with HTTP easier. For example there are the +[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. + +These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, +if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, +then you will want a class with a nice object-oriented interface that you can use in your application instead. + +Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The +standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php +projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. + +As this is a widely adopted standard there are already several implementations available for us to use. I will choose +the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. + +Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a +[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. + +Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use +that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding +the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). + + +to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit +enter + +Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): + +```php +$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); +$response = new \Laminas\Diactoros\Response; +$response->getBody()->write('Hello World! '); +$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); +``` + +This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. + +In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the +write()-Method on that object. + + +To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: + +```php +echo $response->getBody(); +``` + +This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only +stores data. + +You can play around with the other methods of the Request object and take a look at its content with the dd() function. + +```php +dd($response) +``` + +Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot +be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which +creates a copy of the request object with the changed property and returns that clone: + +```php +$response = $response->withStatus(200); +$response = $response->withAddedHeader('Content-type', 'application/json'); +``` + +If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been +defined this way. + +But if you have been keeping attention you might argue that the following line should not work if the request object is +immutable. + +```php +$response->getBody()->write('Hello World!'); +``` + +The response-body implements a stream interface which is immutable for some reasons that are described in the +[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be +aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in +the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. +I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content +to a response object. + +```php +$body = $response->getBody(); +$body->write('Hello World!'); +$response = $response->withBody($body); +``` + +Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our +output-logic a little bit more. Replace the line that echos the response-body with the following: + +```php +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); +``` + +This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a +webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. + +Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. + +Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter + + +[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/05-router.md b/05-router.md deleted file mode 100644 index 44c4ddb..0000000 --- a/05-router.md +++ /dev/null @@ -1,81 +0,0 @@ -[<< previous](04-http.md) | [next >>](06-dispatching-to-a-class.md) - -### Router - -A router dispatches to different handlers depending on rules that you have set up. - -With your current setup it does not matter what URL is used to access the application, it will always result in the same response. So let's fix that now. - -I will use [FastRoute](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own favorite package. - -Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) - -By now you know how to install Composer packages, so I will leave that to you. - -Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. - -```php -$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello-world', function () { - echo 'Hello World'; - }); - $r->addRoute('GET', '/another-route', function () { - echo 'This works too'; - }); -}); - -$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getPath()); -switch ($routeInfo[0]) { - case \FastRoute\Dispatcher::NOT_FOUND: - $response->setContent('404 - Page not found'); - $response->setStatusCode(404); - break; - case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: - $response->setContent('405 - Method not allowed'); - $response->setStatusCode(405); - break; - case \FastRoute\Dispatcher::FOUND: - $handler = $routeInfo[1]; - $vars = $routeInfo[2]; - call_user_func($handler, $vars); - break; -} -``` - -In the first part of the code, you are registering the available routes for your application. In the second part, the dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, the handler callable will be executed. - -This setup might work for really small applications, but once you start adding a few routes your bootstrap file will quickly get cluttered. So let's move them out into a separate file. - -Create a `Routes.php` file in the `src/` folder. It should look like this: - -```php -addRoute($route[0], $route[1], $route[2]); - } -}; - -$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); -``` - -This is already an improvement, but now all the handler code is in the `Routes.php` file. This is not optimal, so let's fix that in the next part. - -Don't forget to commit your changes at the end of each chapter. - -[<< previous](04-http.md) | [next >>](06-dispatching-to-a-class.md) diff --git a/06-dispatching-to-a-class.md b/06-dispatching-to-a-class.md deleted file mode 100644 index 7df552a..0000000 --- a/06-dispatching-to-a-class.md +++ /dev/null @@ -1,56 +0,0 @@ -[<< previous](05-router.md) | [next >>](07-inversion-of-control.md) - -### Dispatching to a Class - -In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) and the followup posts. - -So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). - -We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Controllers` because that will be familiar for the people coming from a framework background. You could also name them `Handlers`. - -Create a new folder inside the `src/` folder with the name `Controllers`.In this folder we will place all our controller classes. In there, create a `Homepage.php` file. - -```php -$method($vars); - break; -``` - -So instead of just calling a method you are now instantiating an object and then calling the method on it. - -Now if you visit `http://localhost:8000/` everything should work. If not, go back and debug. And of course don't forget to commit your changes. - -[<< previous](05-router.md) | [next >>](07-inversion-of-control.md) diff --git a/06-router.md b/06-router.md new file mode 100644 index 0000000..6c39ae5 --- /dev/null +++ b/06-router.md @@ -0,0 +1,101 @@ +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) + +### Router + +A router dispatches to different handlers depending on rules that you have set up. + +With your current setup it does not matter what URL is used to access the application, it will always result in the same +response. So let's fix that now. + +I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own +favorite package. + +Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) + +By now you know how to install Composer packages, so I will leave that to you. + +Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. + +```php +$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +switch ($routeInfo[0]) { + case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: + $response = (new \Laminas\Diactoros\Response)->withStatus(405); + $response->getBody()->write('Method not allowed'); + $response = $response->withStatus(405); + break; + case \FastRoute\Dispatcher::FOUND: + $handler = $routeInfo[1]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + /** @var \Psr\Http\Message\ResponseInterface $response */ + $response = call_user_func($handler, $request); + break; + case \FastRoute\Dispatcher::NOT_FOUND: + default: + $response = (new \Laminas\Diactoros\Response)->withStatus(404); + $response->getBody()->write('Not Found!'); + break; +} +``` + +In the first part of the code, you are registering the available routes for your application. In the second part, the +dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, +we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. +If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. + +This setup might work for really small applications, but once you start adding a few routes your bootstrap file will +quickly get cluttered. So let's move them out into a separate file. + +Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; + +```php +addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}; +``` + +Now let's rewrite the route dispatcher part to use the `Routes.php` file. + +```php +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); +``` + +This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. + +Of course we now need to add the 'config' folder to the configuration files of our +devhelpers so that they can scan that directory as well. + +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/07-dispatching-to-a-class.md b/07-dispatching-to-a-class.md new file mode 100644 index 0000000..0c961a4 --- /dev/null +++ b/07-dispatching-to-a-class.md @@ -0,0 +1,137 @@ +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) + +### Dispatching to a Class + +In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). +MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to +learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) +and the followup posts. + +So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). + +We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other +common names are 'Controllers' or 'Actions'. + +Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. +In there, create a `Hello.php` file. + +```php +getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + } +} +``` + +You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) +that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is +fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement +it in some other parts of our application as well. In order to use that Interface we have to require it with composer: +'composer require psr/http-server-handler'. + +The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. +At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. + +Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: + +```php +return function(\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); + $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); +}; +``` + +Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared +the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing +the response to the one we defined for the second route. + +To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: + +```php +case \FastRoute\Dispatcher::FOUND: + $handler = new $routeInfo[1]; + if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { + throw new \Exception('Invalid Requesthandler'); + } + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + assert($response instanceof \Psr\Http\Message\ResponseInterface) + break; +``` + +So instead of just calling a method you are now instantiating an object and then calling the method on it. + +Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. + +And of course don't forget to commit your changes. + +Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still +generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for +example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still +have our whoopsie error-handler but i like to be more explicit in my control flow. + +In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new +Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and +InternalServerError. All three should extend phps Base Exception class. + +Here is my NotFound.php for example. + +```php + $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody()->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} +``` + +Check if our code still works, try to trigger some errors, run phpstan and the fix command +and don't forget to commit your changes. + +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/07-inversion-of-control.md b/07-inversion-of-control.md deleted file mode 100644 index 6149cc4..0000000 --- a/07-inversion-of-control.md +++ /dev/null @@ -1,51 +0,0 @@ -[<< previous](06-dispatching-to-a-class.md) | [next >>](08-dependency-injector.md) - -### Inversion of Control - -In the last part you have set up a controller class and generated output with `echo`. But let's not forget that we have a nice object oriented HTTP abstraction available. But right now it's not accessible inside your class. - -The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). - -If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is implemented, it will make sense. - -Change your `Homepage` controller to the following: - -```php -response = $response; - } - - public function show() - { - $this->response->setContent('Hello World'); - } -} -``` - -Note that we are [importing](http://php.net/manual/en/language.namespaces.importing.php) `Http\Response` at the top of the file. This means that whenever you use `Response` inside this file, it will resolve to the fully qualified name. - -In the constructor we are now explicitly asking for a `Http\Response`. In this case, `Http\Response` is an interface. So any class that implements the interface can be injected. See [type hinting](http://php.net/manual/en/language.oop5.typehinting.php) and [interfaces](http://php.net/manual/en/language.oop5.interfaces.php) for reference. - -Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: - -```php -$class = new $className($response); -$class->$method($vars); -``` - -The `Http\HttpResponse` object implements the `Http\Response` interface, so it fulfills the contract and can be used. - -Now everything should work again. But if you follow this example, all your objects that are instantiated this way will have the same objects injected. This is of course not good, so let's fix that in the next part. - -[<< previous](06-dispatching-to-a-class.md) | [next >>](08-dependency-injector.md) diff --git a/08-dependency-injector.md b/08-dependency-injector.md deleted file mode 100644 index 4469392..0000000 --- a/08-dependency-injector.md +++ /dev/null @@ -1,97 +0,0 @@ -[<< previous](07-inversion-of-control.md) | [next >>](09-templating.md) - -### Dependency Injector - -A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when the class is instantiated. - -There is only one injector that I can recommend: [Auryn](https://github.com/rdlowrey/Auryn). Sadly all the alternatives that I am aware of are using the [service locator antipattern](http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/) in their documentation and examples. - -Install the Auryn package and then create a new file called `Dependencies.php` in your `src/` folder. In there add the following content: - -```php -alias('Http\Request', 'Http\HttpRequest'); -$injector->share('Http\HttpRequest'); -$injector->define('Http\HttpRequest', [ - ':get' => $_GET, - ':post' => $_POST, - ':cookies' => $_COOKIE, - ':files' => $_FILES, - ':server' => $_SERVER, -]); - -$injector->alias('Http\Response', 'Http\HttpResponse'); -$injector->share('Http\HttpResponse'); - -return $injector; -``` - -Make sure you understand what `alias`, `share` and `define` are doing before you continue. You can read about them in the [Auryn documentation](https://github.com/rdlowrey/Auryn). - -You are sharing the HTTP objects because there would not be much point in adding content to one object and then returning another one. So by sharing it you always get the same instance. - -The alias allows you to type hint the interface instead of the class name. This makes it easy to switch the implementation without having to go back and edit all your classes that use the old implementation. - -Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the injector now so that we are using the same instance of those objects everywhere. - -```php -$injector = include('Dependencies.php'); - -$request = $injector->make('Http\HttpRequest'); -$response = $injector->make('Http\HttpResponse'); -``` - -The other part that has to be changed is the dispatching of the route. Before you had the following code: - -```php -$class = new $className($response); -$class->$method($vars); -``` - -Change that to the following: - -```php -$class = $injector->make($className); -$class->$method($vars); -``` - -Now all your controller constructor dependencies will be automatically resolved with Auryn. - -Go back to your `Homepage` controller and change it to the following: - -```php -request = $request; - $this->response = $response; - } - - public function show() - { - $content = '

Hello World

'; - $content .= 'Hello ' . $this->request->getParameter('name', 'stranger'); - $this->response->setContent($content); - } -} -``` - -As you can see now the class has two dependencies. Try to access the page with a GET parameter like this `http://localhost:8000/?name=Arthur%20Dent`. - -Congratulations, you have now successfully laid the groundwork for your application. - -[<< previous](07-inversion-of-control.md) | [next >>](09-templating.md) diff --git a/08-inversion-of-control.md b/08-inversion-of-control.md new file mode 100644 index 0000000..21f4f23 --- /dev/null +++ b/08-inversion-of-control.md @@ -0,0 +1,54 @@ +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) + +### Inversion of Control + +In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we +want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then +we would need to edit every one of our request handlers to call a different constructor of the class. + +The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that +instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done +with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). + +If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is +implemented, it will make sense. + +Change your `Hello` action to the following: + +```php +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: + +```php +$handler = new $className($response); +``` + +Of course we need to also update all the other handlers. + +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/09-dependency-injector.md b/09-dependency-injector.md new file mode 100644 index 0000000..7f7c6a2 --- /dev/null +++ b/09-dependency-injector.md @@ -0,0 +1,213 @@ +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) + +### Dependency Injector + +A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when +the class is instantiated. + +Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work +with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look +for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). + +I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) +out of the box. + +After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: + +```php +addDefinitions([ + \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), +]); + +return $builder->build(); +``` + +In this file we create a containerbuilder, add some definitions to it and return the container. +As the container supports autowiring we only need to define services where we want to use a specific implementation of +an interface. + +In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to +tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second +exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. + +Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), +as we will use that extensively. + +Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally +to create our Handler-Object + +```php +$container = require __DIR__ . '/../config/dependencies.php'; +assert($container instanceof \Psr\Container\ContainerInterface); + +$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); +assert($request instanceof \Psr\Http\Message\ServerRequestInterface); +``` + +The other part that has to be changed is the dispatching of the route. Before you had the following code: + +```php +$className = $routeInfo[1]; +$handler = new $className($response); +assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Change that to the following: + +```php +/** @var RequestHandlerInterface $handler */ +$className = $routeInfo[1]; +$handler = $container->get($className); +assert($handler instanceof RequestHandlerInterface); +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Make sure to use the container fetch the response object in the catch blocks as well: + +```php +} catch (MethodNotAllowed) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(404); + $response->getBody()->write('Not Found'); +} +``` + +Now all your controller constructor dependencies will be automatically resolved with PHP-DI. + +We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons +in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call +`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we +want to work with a different date in a test. + +Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation +that creates us a DateTimeImmutable object with the current date and time. + +src/Service/Time/Now.php: +```php +namespace Lubian\NoFramework\Service\Time; + +interface Now +{ + public function __invoke(): \DateTimeImmutable; +} +``` +src/Service/Time/SystemClockNow.php: +```php +namespace Lubian\NoFramework\Service\Time; + +final class SystemClockNow implements Now +{ + + public function __invoke(): \DateTimeImmutable + { + return new \DateTimeImmutable; + } +} +``` +If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and +update the handle-method to use the new class property: + +```php +getAttribute('name', 'Stranger'); + $nowAsString = ($this->now)()->format('H:i:s'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowAsString); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI +automatically figures out what classes are requested in the constructor and tries to create the objects needed. + +But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred +S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: + +```php + public function __construct( + private ResponseInterface $response, + private Now $now, + ) +``` + +When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation +should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: + +```php +\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), +``` + +we could also use the PHP-DI create method to delegate the object creation to the container implementation: +```php +\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), +``` + +this way the container can try to resolve any dependencies that the class might have internally, but prefer the other +method because we are not depending on this specific dependency injection implementation. + +Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are +requesting the Hello action. + +If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As +we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way +we wont get any warnings for this particular issue, but for any other issues we add to our code. + +Update the phpstan.neon file to include a "baseline" file: + +``` +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` + +if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and +ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just +'baseline' + +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/09-templating.md b/09-templating.md deleted file mode 100644 index 08720de..0000000 --- a/09-templating.md +++ /dev/null @@ -1,141 +0,0 @@ -[<< previous](08-dependency-injector.md) | [next >>](10-dynamic-pages.md) - -### Templating - -A template engine is not necessary with PHP because the language itself can take care of that. But it can make things like escaping values easier. They also make it easier to draw a clear line between your application logic and the template files which should only put your variables into the HTML code. - -A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. - -For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install that package before you continue (`composer require mustache/mustache`). - -Another well known alternative would be [Twig](http://twig.sensiolabs.org/). - -Now please go and have a look at the source code of the [engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class does not implement an interface. - -You could just type hint against the concrete class. But the problem with this approach is that you create tight coupling. - -In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want to add functionality to the engine. You can't do that without going back and changing all your code that is tightly coupled. - -What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need another implementation, you just implement that interface in your new class and inject the new class instead. - -Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). This sounds a lot more complicated than it is, so just follow along. - -First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. A class can extend multiple interfaces if necessary. - -So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a new folder in your `src/` folder with the name `Template` where you can put all the template related things. - -In there create a new interface `Renderer.php` that looks like this: - -```php -engine = $engine; - } - - public function render($template, $data = []) : string - { - return $this->engine->render($template, $data); - } -} -``` - -As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple and only fulfills the interface. - -Of course we also have to add a definition in our `Dependencies.php` file because otherwise the injector won't know which implementation he has to inject when you hint for the interface. Add this line: - -`$injector->alias('Example\Template\Renderer', 'Example\Template\MustacheRenderer');` - -Now in your `Homepage` controller, add the new dependency like this: - -```php -request = $request; - $this->response = $response; - $this->renderer = $renderer; - } - -... -``` - -We also have to rewrite the `show` method. Please note that while we are just passing in a simple array, Mustache also gives you the option to pass in a view context object. We will go over this later, for now let's keep it as simple as possible. - -```php - public function show() - { - $data = [ - 'name' => $this->request->getParameter('name', 'stranger'), - ]; - $html = $this->renderer->render('Hello {{name}}', $data); - $this->response->setContent($html); - } -``` - -Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. But what we want is template files, so let's go back and change that. - -To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the `Dependencies.php` file and add the following code: - -```php -$injector->define('Mustache_Engine', [ - ':options' => [ - 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__DIR__) . '/templates', [ - 'extension' => '.html', - ]), - ], -]); -``` - -We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have to rename all the template files. - -In your project root folder, create a `templates` folder. In there, create a file `Homepage.html`. The content of the file should look like this: - -``` -

Hello World

-Hello {{ name }} -``` - -Now you can go back to your `Homepage` controller and change the render line to `$html = $this->renderer->render('Homepage', $data);` - -Navigate to the hello page in your browser to make sure everything works. And as always, don't forget to commit your changes. - -[<< previous](08-dependency-injector.md) | [next >>](10-dynamic-pages.md) diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md deleted file mode 100644 index 398ba81..0000000 --- a/10-dynamic-pages.md +++ /dev/null @@ -1,205 +0,0 @@ -[<< previous](09-templating.md) | [next >>](11-page-menu.md) - -### Dynamic Pages - -So far we only have a static page with not much functionality. Just having a hello world example is not very useful, so let's go beyond that and add some real functionality to our application. - -Our first feature will be dynamic pages generated from [markdown](http://en.wikipedia.org/wiki/Markdown) files. - -Create a `Page` controller with the following content: - -```php -pageFolder = $pageFolder; - } - - public function readBySlug(string $slug) : string - { - return 'I am a placeholder'; - } -} -``` - -As you can see we are requiring the page folder path as a constructor argument. This makes the class flexible and if we decide to move files or write unit tests for the class, we can easily change the location with the constructor argument. - -You could also put the page related things into it's own package and reuse it in different applications. Because we are not tightly coupling things, things are very flexible. - -This will do for now. Let's create a template file for our pages with the name `Page.html` in the `templates` folder. For now just add `{{ content }}` in there. - -Add the following to your `Dependencies.php` file so that the application know which implementation to inject for our new interface. We also define the the `pageFolder` there. - -```php -$injector->define('Example\Page\FilePageReader', [ - ':pageFolder' => __DIR__ . '/../pages', -]); - -$injector->alias('Example\Page\PageReader', 'Example\Page\FilePageReader'); -$injector->share('Example\Page\FilePageReader'); -``` - - -Now go back to the `Page` controller and change the `show` method to the following: - -```php -public function show($params) -{ - $slug = $params['slug']; - $data['content'] = $this->pageReader->readBySlug($slug); - $html = $this->renderer->render('Page', $data); - $this->response->setContent($html); -} -``` - -To make this work, we will need to inject a `Response`, `Renderer` and a `PageReader`. I will leave this to you as an exercise. Remember to `use` all the proper namespaces. Use the `Homepage` controller as a reference. - -Did you get everything to work? - -If not, this is how the beginning of your controller should look now: - -```php -response = $response; - $this->renderer = $renderer; - $this->pageReader = $pageReader; - } -... -``` - -So far so good, now let's make our `FilePageReader` actually do some work. - -We need to be able to communicate that a page was not found. For this we can create a custom exception that we can catch later. In your `src/Page` folder, create a `InvalidPageException.php` file with this content: - -```php -pageFolder/$slug.md"; - -if (!file_exists($path)) { - throw new InvalidPageException($slug); -} - -return file_get_contents($path); -``` - -Now if you navigate to a page that does not exist, you should see an `InvalidPageException`. If a file exists, you should see the content. - -Of course showing the user an exception for an invalid URL does not make sense. So let's catch the exception and show a 404 error instead. - -Go to your `Page` controller and refactor the `show` method so that it looks like this: - -```php -public function show($params) -{ - $slug = $params['slug']; - - try { - $data['content'] = $this->pageReader->readBySlug($slug); - } catch (InvalidPageException $e) { - $this->response->setStatusCode(404); - return $this->response->setContent('404 - Page not found'); - } - - $html = $this->renderer->render('Page', $data); - $this->response->setContent($html); -} -``` - -Make sure that you use an `use` statement for the `InvalidPageException` at the top of the file. - -Try a few different URLs to check that everything is working as it should. If something is wrong, go back and debug it until it works. - -And as always, don't forget to commit your changes. - -[<< previous](09-templating.md) | [next >>](11-page-menu.md) diff --git a/10-invoker.md b/10-invoker.md new file mode 100644 index 0000000..3033fae --- /dev/null +++ b/10-invoker.md @@ -0,0 +1,102 @@ +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) + +### Invoker + +Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the +one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long +with some services and not the whole Requestobject with all its various properties. + +If we take our Hello action for example we only need a response object, the time service and the 'name' information from +the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named +the class hello and it would be redundant to also call the the method hello. So an updated version of that class could +look like this: + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + string $name = 'Stranger', + ): ResponseInterface + { + $body = $this->response->getBody(); + $nowString = $now->get()->format('H:i:s'); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowString); + return $response + ->withBody($body) + ->withStatus(200); + } +} +``` + +It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short +closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the +rootpath of our application yet. + +```php +$r->addRoute('GET', '/hello[/{name}]', Hello::class); +$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); +$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +``` + +In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the +route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that +class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what +arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router +if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple +callable we would need to manually use reflection as well to resolve all the arguments. + +But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) +which handles all of that for us. + +After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo +switch section in the Bootstrap.php file to use the container->call() method: + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2]; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$response = $container->call($handler, $args); +``` + +Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. + +But by now you should know that I do not like to depend on specific implementations and the call method is not defined in +the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container +or any other implementation. + +Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific +container implementation so we could use it with any container that implements the ContainerInterface. And best of all +the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that +we could implement if we ever want to write our own implementation or we could write an adapter that uses a different +class that solves the same problem. + +But for now we are using the solution provided by PHP-DI. +So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2] ?? []; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$invoker = $container->get(InvokerInterface::class); +assert($invoker instanceof InvokerInterface); +$response = $invoker->call($handler, $args); +assert($response instanceof ResponseInterface); +``` + +Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) +by php, and even some more. + +But let us move on to something more fun and add some templating functionality to our application as we are trying to build +a website in the end. + +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/11-page-menu.md b/11-page-menu.md deleted file mode 100644 index d45849e..0000000 --- a/11-page-menu.md +++ /dev/null @@ -1,260 +0,0 @@ -[<< previous](10-dynamic-pages.md) | [next >>](12-frontend.md) - -### Page Menu - -Now we have made a few nice dynamic pages. But nobody can find them. - -Let's fix that now. In this chapter we will create a menu with links to all of our pages. - -When we have a menu, we will want to be able to reuse the same code on multiple pages. We could create a separate file and include it every time, but there is a better solution. - -It is more practical to have templates that are able to extend other templates, like a layout for example. Then we can have all the layout related code in a single file and we don't have to include header and footer files in every template. - -Our implementation of mustache does not support this. We could write code to work around this, which will take time and could introduce some bugs. Or we could switch to a library that already supports this and is well tested. [Twig](http://twig.sensiolabs.org/) for example. - -Now you might wonder why we didn't start with Twig right away. Because this is a good example to show why using interfaces and writing loosely-coupled code is a good idea. Like in the real world, the requirements suddenly changed and now our code needs to adapt. - -Remember how you created a `MustacheRenderer` in [chapter 9](09-templating.md)? This time, we create a `TwigRenderer` that implements the same interface. - -But before we start, install the latest version of Twig with composer (`composer require "twig/twig:~1.0"`). - -Then create the a `TwigRenderer.php` in your `src/Template` folder that looks like this: - -```php -renderer = $renderer; - } - - public function render($template, $data = []) : string - { - return $this->renderer->render("$template.html", $data); - } -} -``` - -As you can see, on the render function call a `.html` is added. This is because Twig does not add a file ending by default and you would have to specifiy it on every call otherwise. By doing it like this, you can use it in the same way as you used Mustache. - -Add the following code to your `Dependencies.php` file: - -```php -$injector->delegate('Twig_Environment', function () use ($injector) { - $loader = new Twig_Loader_Filesystem(dirname(__DIR__) . '/templates'); - $twig = new Twig_Environment($loader); - return $twig; -}); -``` - -Instead of just defining the dependencies, we are using a delegate to give the responsibility to create the class to a function. This will be useful in the future. - -Now you can switch the `Renderer` alias from `MustacheRenderer` to `TwigRenderer`. Now by default Twig will be used instead of Mustache. - -If you have a look at the site in your browser, everything should work now as before. Now let's get started with the actual menu. - -To start we will just send a hardcoded array to the template. Go to you `Homepage` controller and change your `$data` array to this: - -```php -$data = [ - 'name' => $this->request->getParameter('name', 'stranger'), - 'menuItems' => [['href' => '/', 'text' => 'Homepage']], -]; -``` - -At the top of your `Homepage.html` file add this code: - -```php -{% for item in menuItems %} - {{ item.text }}
-{% endfor %} -``` - -Now if you refresh the homepage in the browser, you should see a link. - -The menu works on the homepage, but we want it on all our pages. We could copy it over to all the template files, but that would be a bad idea. Then if something changes, you would have to go change all the files. - -So instead we are going to use a layout that can be used by all the templates. - -Create a `Layout.html` in your `templates` folder with the following content: - -```php -{% for item in menuItems %} - {{ item['text'] }}
-{% endfor %} -
-{% block content %} -{% endblock %} -``` - -Then change your `Homepage.html` to this: - -```php -{% extends "Layout.html" %} -{% block content %} -

Hello World

- Hello {{ name }} -{% endblock %} -``` - -And your `Page.html` to this: - -```php -{% extends "Layout.html" %} -{% block content %} - {{ content }} -{% endblock %} -``` - -If you refresh your homepage now, you should see the menu. But if you go to a subpage, the menu is not there but the `
` line is. - -The problem is that we are only passing the `menuItems` to the homepage. Doing that over and over again for all pages would be a bit tedious and a lot of work if something changes. So let's fix that in the next step. - -We could create a global variable that is usable by all templates, but that is not a good idea here. We will add different parts of the site in the future like an admin area and we will have a different menu there. - -So instead we will use a custom renderer for the frontend. First we create an empty interface that extends the existing `Renderer` interface. - -```php -renderer = $renderer; - } - - public function render($template, $data = []) : string - { - $data = array_merge($data, [ - 'menuItems' => [['href' => '/', 'text' => 'Homepage']], - ]); - return $this->renderer->render($template, $data); - } -} -``` - -As you can see we have a dependency on a `Renderer` in this class. This class is a wrapper for our `Renderer` and adds the `menuItems` to all `$data` arrays. - -Of course we also need to add another alias to the dependencies file. - -```php -$injector->alias('Example\Template\FrontendRenderer', 'Example\Template\FrontendTwigRenderer'); -``` - -Now go to your controllers and exchange all references of `Renderer` with `FrontendRenderer`. Make sure you change it in both the `use` statement at the top and in the constructor. - -Also delete the following line from the `Homepage` controller: - -```php -'menuItems' => [['href' => '/', 'text' => 'Homepage']], -``` - -Once that is done, you should see the menu on both the homepage and your subpages. - -Everything should work now, but it doesn't really make sense that the menu is defined in the `FrontendTwigRenderer`. So let's refactor that and move it into it's own class. - -Right now the menu is defined in the array, but it is very likely that this will change in the future. Maybe you want to define it in the database or maybe you even want to generate it dynamically based on the pages available. We don't have this information and things might change in the future. - -So let's do the right thing here and start with an interface again. But first, create a new folder in the `src` directory for the menu related things. `Menu` sounds like a reasonable name, doesn't it? - -```php - '/', 'text' => 'Homepage'], - ]; - } -} -``` - -This is only a temporary solution to keep things moving forward. We are going to revisit this later. - -Before we continue, let's edit the dependencies file to make sure that our application knows which implementation to use when the interface is requested. - -Add these lines above the `return` statement: - -```php -$injector->alias('Example\Menu\MenuReader', 'Example\Menu\ArrayMenuReader'); -$injector->share('Example\Menu\ArrayMenuReader'); -``` - -Now you need to change out the hardcoded array in the `FrontendTwigRenderer` class to make it use our new `MenuReader` instead. Give it a try without looking at the solution below. - -Did you finish it or did you get stuck? Or are you just lazy? Doesn't matter, here is a working solution: - -```php -renderer = $renderer; - $this->menuReader = $menuReader; - } - - public function render($template, $data = []) : string - { - $data = array_merge($data, [ - 'menuItems' => $this->menuReader->readMenu(), - ]); - return $this->renderer->render($template, $data); - } -} -``` - -Everything still working? Awesome. Commit everything and move on to the next chapter. - -[<< previous](10-dynamic-pages.md) | [next >>](12-frontend.md) diff --git a/11-templating.md b/11-templating.md new file mode 100644 index 0000000..3759664 --- /dev/null +++ b/11-templating.md @@ -0,0 +1,240 @@ +[<< previous](10-invoker.md) | [next >>](12-configuration.md) + +### Templating + +A template engine is not necessary with PHP because the language itself can take care of that. But it can make things +like escaping values easier. They also make it easier to draw a clear line between your application logic and the +template files which should only put your variables into the HTML code. + +A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please +also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. +Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. + +For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install +that package before you continue (`composer require mustache/mustache`). + +Another well known alternative would be [Twig](http://twig.sensiolabs.org/). + +Now please go and have a look at the source code of the +[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class +does not implement an interface. + +You could just type hint against the concrete class. But the problem with this approach is that you create tight +coupling. + +In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the +implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want +to add functionality to the engine. You can't do that without going back and changing all your code that is tightly +coupled. + +What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need +another implementation, you just implement that interface in your new class and inject the new class instead. + +Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). +This sounds a lot more complicated than it is, so just follow along. + +First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). +This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. +A class can implement multiple interfaces if necessary. + +So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a +new folder in your `src/` folder with the name `Template` where you can put all the template related things. + +In there create a new interface `Renderer.php` that looks like this: + +```php + $data + * @return string + */ + public function render(string $template, array $data = []) : string; +} +``` + +Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file +`MustacheRenderer.php` with the following content: + +```php +engine->render($template, $data); + } +} +``` + +As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple +and only fulfills the interface. + +Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know +which implementation he has to inject when you hint for the interface. Add this line: + +```php +[ + ... + \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) + ->constructor(new Mustache_Engine), +] +``` + +Now update the Hello.php class to require an implementation of our renderer interface +and use that to render a string using mustache syntax. + + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + Renderer $renderer, + string $name = 'Stranger', + ): ResponseInterface { + $body = $response->getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render( + 'Hello {{name}}, the time is {{now}}!', + $data, + ); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} +``` + +Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. +But what we want is template files, so let's go back and change that. + +To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the +`dependencies.php` file and add the following code: + +```php +[ + ... + Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), + Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), +] +``` + +We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. +Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have +to rename all the template files. + +To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the +dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader +in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object +we can then use it in the factory. + +In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: + +``` +

Hello World

+Hello {{ name }} +``` + +Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` + +Navigate to the hello page in your browser to make sure everything works. + +One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies +file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration +values. + +Lets create a 'Settings' class in our './src' Folder: + +```php +addDefinitions([ + Settings::class => fn () => require __DIR__ '/settings.php', + ResponseInterface::class => create(Response::class), + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + Renderer::class => fn (ME $me) => new Mustache($me), + MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), +]); + +return $builder->build(); +``` + + + +And as always, don't forget to commit your changes. + + +[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/12-configuration.md b/12-configuration.md new file mode 100644 index 0000000..a44dfd5 --- /dev/null +++ b/12-configuration.md @@ -0,0 +1,201 @@ +[<< previous](11-templating.md) | [next >>](13-refactoring.md) + +### Configuration + +In the last chapter we added some more definitions to our dependencies.php in that definitions +we needed to pass quite a few configuration settings and filesystem strings to the constructors +of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. + +As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. + +As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. + +Lets create a 'Settings' class in our './src' Folder: + +```php +filePath; + } +} +``` + +If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files +and craft a settings object from them. + +As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another +factory for our Container because everyone should have a Friend :) + +```php +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn () => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} +``` + +For this to work we need to change our dependencies.php file to just return the array of definitions: +And here we can instantly use the Settings object to create our template engine. + +```php + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), +]; +``` + +Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: +require __DIR__ . '/../vendor/autoload.php'; + +```php +... +error_reporting(E_ALL); + +$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); +$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); +... +``` + +Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. + +[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/12-frontend.md b/12-frontend.md deleted file mode 100644 index bd30564..0000000 --- a/12-frontend.md +++ /dev/null @@ -1,188 +0,0 @@ -[<< previous](11-page-menu.md) | [next >>](to-be-continued.md) - - -### Frontend - -I don't know about you, but I don't like to work on a site that looks two decades old. So let's improve the look of our little application. - -This is not a frontend tutorial, so we'll just use [pure](http://purecss.io/) and call it a day. - -First we need to change the `Layout.html` template. I don't want to waste your time with HTML and CSS, so I'll just provide the code for you to copy paste. - -```php - - - - - Example - - - - -
- -
-
- {% block content %} - {% endblock %} -
-
-
- - -``` - -You will also need some custom CSS. This is a file that we want publicly accessible. So where do we put it? Exactly, in the public folder. - -But to keep things a little organized, add a `css` folder in there first and then create a `style.css` with the following content: - -```css -body { - color: #777; -} - -#layout { - position: relative; - padding-left: 0; -} - -#layout.active #menu { - left: 150px; - width: 150px; -} - -#layout.active .menu-link { - left: 150px; -} - -.content { - margin: 0 auto; - padding: 0 2em; - max-width: 800px; - margin-bottom: 50px; - line-height: 1.6em; -} - -.header { - margin: 0; - color: #333; - text-align: center; - padding: 2.5em 2em 0; - border-bottom: 1px solid #eee; -} - -.header h1 { - margin: 0.2em 0; - font-size: 3em; - font-weight: 300; -} - -.header h2 { - font-weight: 300; - color: #ccc; - padding: 0; - margin-top: 0; -} - -#menu { - margin-left: -150px; - width: 150px; - position: fixed; - top: 0; - left: 0; - bottom: 0; - z-index: 1000; - background: #191818; - overflow-y: auto; - -webkit-overflow-scrolling: touch; -} - -#menu a { - color: #999; - border: none; - padding: 0.6em 0 0.6em 0.6em; -} - -#menu .pure-menu, -#menu .pure-menu ul { - border: none; - background: transparent; -} - -#menu .pure-menu ul, -#menu .pure-menu .menu-item-divided { - border-top: 1px solid #333; -} - -#menu .pure-menu li a:hover, -#menu .pure-menu li a:focus { - background: #333; -} - -#menu .pure-menu-selected, -#menu .pure-menu-heading { - background: #1f8dd6; -} - -#menu .pure-menu-selected a { - color: #fff; -} - -#menu .pure-menu-heading { - font-size: 110%; - color: #fff; - margin: 0; -} - -.header, -.content { - padding-left: 2em; - padding-right: 2em; -} - -#layout { - padding-left: 150px; /* left col width "#menu" */ - left: 0; -} -#menu { - left: 150px; -} - -.menu-link { - position: fixed; - left: 150px; - display: none; -} - -#layout.active .menu-link { - left: 150px; -} -``` - -Now if you have a look at your site again, things should look a little better. Feel free to further improve the look of it yourself later. But let's continue with the tutorial now. - -Every time that you need an asset or a file publicly available, then you can just put it in your `public` folder. You will need this for all kinds of assets like javascript files, css files, images and more. - -So far so good, but it would be nice if our visitors can see what page they are on. - -Of course we need more than one page in the menu for this. I will just use the `page-one.md` that we created earlier in the tutorial. But feel free to add a few more pages and add them as well. - -Go back to the `ArrayMenuReader` and add your new pages to the array. It should look something like this now: - -```php -return [ - ['href' => '/', 'text' => 'Homepage'], - ['href' => '/page-one', 'text' => 'Page One'], -]; -``` - -[<< previous](11-page-menu.md) | [next >>](to-be-continued.md) - diff --git a/13-refactoring.md b/13-refactoring.md new file mode 100644 index 0000000..067e168 --- /dev/null +++ b/13-refactoring.md @@ -0,0 +1,377 @@ +[<< previous](12-configuration.md) | [next >>](14-middleware.md) + +### Refactoring + +By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no +reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. +After all the bootstrap file should just set up the classes needed for the handling logic and execute them. + +At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. +As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the +method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non +static and extracted an interface. + +'./src/Http/Emitter.php' +```php +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + echo $response->getBody(); + } +} +``` +After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following +code in the Bootstrap.php to emit the response: + +```php +/** @var Emitter $emitter */ +$emitter = $container->get(Emitter::class); +$emitter->emit($response); +``` + +If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily +write an adapter that implements your emitter interface and wraps that more advanced emitter + +Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and +calling the routerhandler that in the passes the request to a function and gets the response. + +For this to steps to be seperated we are going to create two more classes: +1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object +2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the + requestobject, fetches the correct object from the container and calls it to create a response. + +Lets create the HandlerInterface first: + +```php +getAttribute($this->routeAttributeName, false); + assert($handler !== false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} + +``` + +We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. +The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler +as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in +the next chapter. + +```php +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} +``` + +Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. +```php +[ + '...', + Emitter::class => fn (BasicEmitter $e) => $e, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, +], +``` + +And then we can update our Bootstrap.php to fetch all the services and let them handle the request. + +```php +... +$routeMiddleWare = $container->get(MiddlewareInterface::class); +assert($routeMiddleWare instanceof MiddlewareInterface); +$handler = $container->get(RoutedRequestHandler::class); +assert($handler instanceof RequestHandlerInterface); +$emitter = $container->get(Emitter::class); +assert($emitter instanceof Emitter); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$response = $routeMiddleWare->process($request, $handler); +$emitter->emit($response); +``` +Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of +code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). + +So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. + +I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class +should require to function properly. + +* A RequestFactory + We want our Kernel to be able to build the request itself +* An Emitter + Without an Emitter we will not be able to send the response to the client +* RouteMiddleware + To decore the request with the correct handler for the requested route +* RequestHandler + To delegate the request to the correct funtion that creates the response + +As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface +to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our +interface: + +```php +factory::fromGlobals(); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} +``` + +For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: + +```php +routeMiddleware->process($request, $this->handler); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} + +``` + +We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines + +```php +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); +``` + +You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking +at the Whoops output and adding the needed definitions to the dependencies.php file. + +And as always, don't forget to commit your changes. + +[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/14-middleware.md b/14-middleware.md new file mode 100644 index 0000000..f4d3f3b --- /dev/null +++ b/14-middleware.md @@ -0,0 +1,220 @@ +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) + +### Middleware + +In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain +a bit more about what this interface does and why it is used in many applications. + +The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets +passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all +the middlewars to the client/emitter. + +So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the +response after it gets created by our handlers. + +So lets take a look at the middleware and the requesthandler interfaces + +```php +interface MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; +} + +interface RequestHandlerInterface +{ + public function handle(ServerRequestInterface $request): ResponseInterface; +} +``` + +The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a +requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the +response. + +But the middleware could just ignore the handler and produce a response on its own as the interface just requires us +to produce a response. + +A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users +that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the +database. + +In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates +the request with an 'isAuthenticated' attribute. + +If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that +response is not already cached, than we let the handler create the response and store that in the cache for a few +seconds + +```php +interface CacheInterface +{ + public function get(string $key, callable $resolver, int $ttl): mixed; +} +``` + +The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one +defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses +the callable to produce the value and stores it for the time specified in ttl. + +so lets write our caching middleware: + +```php +final class CachingMiddleware implements MiddlewareInterface +{ + public function __construct(private CacheInterface $cache){} + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if ($request->getAttribute('isAuthenticated', false)) { + $key = $request->getUri()->getPath(); + return $this->cache->get($key, fn() => $handler->handle($request), 10); + } + return $handler->handle($request); + } +} +``` + +we can also modify the response after it has been created by our application, for example we could implement a gzip +middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser +know that our application is used to serve dank memes: + +```php +final class DankMemeMiddleware implements MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $response = $handler->handle($request); + return $response->withAddedHeader('Meme', 'Dank'); + } +} +``` + +but for our application we are going to just add two external middlewares: + +* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. +* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware + +```bash +composer require middlewares/trailing-slash +composer require middlewares/whoops +``` + +The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the +application as well as the middleware stack. + +Our desired request -> response flow looks something like this: + + Client + | ^ + v | + Kernel + | ^ + v | + Whoops Middleware + | ^ + v | + TrailingSlash + | ^ + v | + Routing + | ^ + v | + ContainerResolver + | ^ + v | + Controller/Action + +As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every +middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. + +```php +interface Pipeline +{ + public function dispatch(ServerRequestInterface $request): ResponseInterface; +} +``` + +And our implementation looks something like this: + +```php +final class SimplePipeline implements Pipeline +{ + /** + * @param MiddlewareInterface[] $middlewares + * @param RequestHandlerInterface $tip + */ + public function __construct( + private readonly array $middlewares, + private RequestHandlerInterface $tip + ){} + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + $this->tip = new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $next + ){} + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->next); + } + }; + } + } +} +``` + +Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code +that should produce our response. + +In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument +and store that itself as the current tip. + +There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) + +Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline: +```php + Pipeline::class => function ( + RouteDecoratedRequestHandler $tip, + RouteDecorationMiddleware $router, + ) { + $middlewares = require __DIR__ . '/middlewares.php'; + $middlewares[] = $router; + return new SimplePipeline($middlewares, $tip); + }, +``` +And of course a new file called middlewares.php in our config folder: +```php +pipeline->dispatch($request); +} +``` + +Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our +pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a +dependency container, if you want you can do that as well. + +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) diff --git a/README.md b/README.md index ad2da80..af7b810 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,3 @@ -Because this tutorial was so well-received, it inspired me to write a book. The book is a much more up to date version of this tutorial and covers a lot more. Click the link below to check it out (there is also a sample chapter available). +# No Framework +[Start](01-front-controller.md) -### [Professional PHP: Building maintainable and secure applications](http://patricklouys.com/professional-php/) - -![](http://patricklouys.com/img/professional-php-thumb.png) - -The tutorial is still available in it's original form below. - -## Create a PHP application without a framework - -### Introduction - -If you are new to the language, this tutorial is not for you. This tutorial is aimed at people who have grasped the basics of PHP and know a little bit about object-oriented programming. - -You should have at least heard of [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29). If you are not familiar with it, now would be a good time to familiarize yourself with the principles before you start with the tutorial. - -I saw a lot of people coming into the Stack Overflow PHP chatroom and asking if framework X is any good. Most of the time the answer was that they should just use PHP and not a framework to build their application. But many are overwhelmed by this and don't know where to start. - -So my goal with this is to provide an easy resource that people can be pointed to. In most cases a framework does not make sense and writing an application from scratch with the help of some third party packages is much, much easier than some people think. - -**This tutorial was written for PHP 7.0 or newer versions.** If you are using an older version, please upgrade it before you start. I recommend that you use the [current stable version](http://php.net/downloads.php). - -So let's get started right away with the [first part](01-front-controller.md). - -### Parts - -1. [Front Controller](01-front-controller.md) -2. [Composer](02-composer.md) -3. [Error Handler](03-error-handler.md) -4. [HTTP](04-http.md) -5. [Router](05-router.md) -6. [Dispatching to a Class](06-dispatching-to-a-class.md) -7. [Inversion of Control](07-inversion-of-control.md) -8. [Dependency Injector](08-dependency-injector.md) -9. [Templating](09-templating.md) -10. [Dynamic Pages](10-dynamic-pages.md) -11. [Page Menu](11-page-menu.md) -12. [Frontend](12-frontend.md) diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..0b30662 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,22 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +Vagrant.configure("2") do |config| + config.vm.box = "archlinux/archlinux" + config.vm.network "forwarded_port", guest: 1234, host: 1234 + config.vm.network "forwarded_port", guest: 22, host: 2200, id: 'ssh' + config.vm.synced_folder "./app", "/home/vagrant/app" + config.ssh.username = 'vagrant' + config.ssh.password = 'vagrant' + config.vm.provision "shell", inline: <<-SHELL + pacman -Syu --noconfirm + pacman -S --noconfirm php php-sqlite php-intl php-sodium php-apcu composer xdebug vim + echo '127.0.0.1 localhost' >> /etc/hosts + echo -e 'extension=pdo_sqlite\nextenstion=sqlite3\n' >> /etc/php/conf.d/tutorial.ini + echo -e 'extension=apcu\nzend_extension=opcache\n' >> /etc/php/conf.d/tutorial.ini + echo -e 'zend_extension=xdebug\nxdebug.client_host=10.0.2.2\n' >> /etc/php/conf.d/tutorial.ini + echo -e 'xdebug.client_port=9003\nxdebug.mode=debug\n' >> /etc/php/conf.d/tutorial.ini + echo -e 'zend.assertions=1\n' >> /etc/php/conf.d/tutorial.ini + + SHELL +end diff --git a/app/public/favicon.ico b/app/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/app/src/.gitkeep b/app/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/src/Http/AddRoute.php b/app/src/Http/AddRoute.php new file mode 100644 index 0000000..0400347 --- /dev/null +++ b/app/src/Http/AddRoute.php @@ -0,0 +1,17 @@ +|class-string|callable $handler + */ + public function addRoute( + string $method, + string $path, + array|string|callable $handler, + ): self; +} \ No newline at end of file diff --git a/app/src/Http/RouteDecorationMiddleware.php b/app/src/Http/RouteDecorationMiddleware.php new file mode 100644 index 0000000..ff2c846 --- /dev/null +++ b/app/src/Http/RouteDecorationMiddleware.php @@ -0,0 +1,97 @@ + $routes + */ + public function __construct( + private readonly ResponseFactoryInterface $responseFactory, + private readonly string $routeAttributeName = '__route_handler', + private array $routes = [], + private Dispatcher|null $dispatcher = null, + ) + { + } + + /** + * @throws MethodNotAllowed + * @throws NotFound + */ + private function decorateRequest(ServerRequestInterface $request): ServerRequestInterface + { + $this->dispatcher ??= $this->createDispatcher(); + $routeInfo = $this->dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath() + ); + + if ($routeInfo[0] === Dispatcher::NOT_FOUND) { + throw new NotFound; + } + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + + return $request->withAttribute($this->routeAttributeName, $routeInfo[1]); + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + + return $handler->handle($request); + } + + private function createDispatcher(): Dispatcher + { + return simpleDispatcher(function (RouteCollector $r) { + foreach ($this->routes as $route) { + $r->addRoute($route[0], $route[1], $route[2]); + } + }); + } + + /** + * @inheritDoc + */ + public function addRoute(string $method, string $path, array|string|callable $handler,): AddRoute + { + $this->routes[] = [$method, $path, $handler]; + return $this; + } +} diff --git a/app/src/Kernel.php b/app/src/Kernel.php new file mode 100644 index 0000000..85c0ee8 --- /dev/null +++ b/app/src/Kernel.php @@ -0,0 +1,63 @@ +routeMiddleware->process($request, $this->handler); + } + + public function run(ServerRequestInterface |null $request = null): void + { + $request ??= $this->createRequest(); + $response = $this->handle($request); + $this->emitter->emit($response); + } + + private function createRequest(): ServerRequestInterface + { + try { + $request = $this->container->get(ServerRequestInterface::class); + assert($request instanceof ServerRequestInterface); + return $request; + } catch (Throwable $t) { + throw new InternalServerError( + 'could not get Request from container, please configure the container ' . + 'in order to use run() wihtout a request', + $t->getCode(), + $t, + ); + } + } + + public function addRoute( + string $method, + string $path, + array|string|callable $handler): AddRoute + { + $this->routeMiddleware->addRoute($method, $path, $handler); + return $this; + } +} diff --git a/implementation/01-front-controller/public/index.php b/implementation/01-front-controller/public/index.php new file mode 100644 index 0000000..43d37a2 --- /dev/null +++ b/implementation/01-front-controller/public/index.php @@ -0,0 +1,5 @@ +=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "phpstan/phpstan", + "version": "1.4.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.4.10" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-14T10:25:45+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/02-composer/public/index.php b/implementation/02-composer/public/index.php new file mode 100644 index 0000000..43d37a2 --- /dev/null +++ b/implementation/02-composer/public/index.php @@ -0,0 +1,5 @@ +=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "phpstan/phpstan", + "version": "1.4.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.4.10" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-14T10:25:45+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/03-error-handler/public/index.php b/implementation/03-error-handler/public/index.php new file mode 100644 index 0000000..43d37a2 --- /dev/null +++ b/implementation/03-error-handler/public/index.php @@ -0,0 +1,5 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (\Throwable $e) { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +throw new \Exception("Ooooopsie"); diff --git a/implementation/04-dev-helpers/.php-cs-fixer.php b/implementation/04-dev-helpers/.php-cs-fixer.php new file mode 100644 index 0000000..3db1195 --- /dev/null +++ b/implementation/04-dev-helpers/.php-cs-fixer.php @@ -0,0 +1,41 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'linebreak_after_opening_tag' => true, + 'native_constant_invocation' => true, + 'native_function_invocation' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha', + 'imports_order' => [ + 'const', + 'class', + 'function', + ], + ], + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__ . '/src') + ); \ No newline at end of file diff --git a/implementation/04-dev-helpers/composer.json b/implementation/04-dev-helpers/composer.json new file mode 100644 index 0000000..995d677 --- /dev/null +++ b/implementation/04-dev-helpers/composer.json @@ -0,0 +1,29 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "scripts": { + "serve": "php -S localhost:1234 -t ./public", + "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", + "phpstan": "./vendor/bin/phpstan analyze", + "style": "./vendor/bin/php-cs-fixer fix" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "php-cs-fixer/shim": "^3.7", + "symfony/var-dumper": "^6.0" + } +} diff --git a/implementation/04-dev-helpers/composer.lock b/implementation/04-dev-helpers/composer.lock new file mode 100644 index 0000000..8ac2710 --- /dev/null +++ b/implementation/04-dev-helpers/composer.lock @@ -0,0 +1,420 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "e956f9f7a53de7c1c149a093926ab371", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "php-cs-fixer/shim", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" + }, + "time": "2022-03-07T17:02:59+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.4.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.4.10" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-14T10:25:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/04-dev-helpers/phpstan.neon b/implementation/04-dev-helpers/phpstan.neon new file mode 100644 index 0000000..c2f33f3 --- /dev/null +++ b/implementation/04-dev-helpers/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: 9 + paths: + - src \ No newline at end of file diff --git a/implementation/04-dev-helpers/public/index.php b/implementation/04-dev-helpers/public/index.php new file mode 100644 index 0000000..32f5eb3 --- /dev/null +++ b/implementation/04-dev-helpers/public/index.php @@ -0,0 +1,5 @@ +pushHandler(new PrettyPageHandler()); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +throw new Exception("Ooooopsie"); diff --git a/implementation/05-http/.php-cs-fixer.php b/implementation/05-http/.php-cs-fixer.php new file mode 100644 index 0000000..3db1195 --- /dev/null +++ b/implementation/05-http/.php-cs-fixer.php @@ -0,0 +1,41 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'linebreak_after_opening_tag' => true, + 'native_constant_invocation' => true, + 'native_function_invocation' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha', + 'imports_order' => [ + 'const', + 'class', + 'function', + ], + ], + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__ . '/src') + ); \ No newline at end of file diff --git a/implementation/05-http/composer.json b/implementation/05-http/composer.json new file mode 100644 index 0000000..a576724 --- /dev/null +++ b/implementation/05-http/composer.json @@ -0,0 +1,30 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "scripts": { + "serve": "php -S localhost:1234 -t ./public", + "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", + "phpstan": "./vendor/bin/phpstan analyze", + "style": "./vendor/bin/php-cs-fixer fix" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "php-cs-fixer/shim": "^3.7", + "symfony/var-dumper": "^6.0" + } +} diff --git a/implementation/05-http/composer.lock b/implementation/05-http/composer.lock new file mode 100644 index 0000000..c8f686e --- /dev/null +++ b/implementation/05-http/composer.lock @@ -0,0 +1,627 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "8f46f82d7ff0a4180389107e37ae5ed0", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "php-cs-fixer/shim", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" + }, + "time": "2022-03-07T17:02:59+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.4.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.4.10" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-14T10:25:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/05-http/phpstan-baseline.neon b/implementation/05-http/phpstan-baseline.neon new file mode 100644 index 0000000..e69de29 diff --git a/implementation/05-http/phpstan.neon b/implementation/05-http/phpstan.neon new file mode 100644 index 0000000..c2f33f3 --- /dev/null +++ b/implementation/05-http/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: 9 + paths: + - src \ No newline at end of file diff --git a/implementation/05-http/public/index.php b/implementation/05-http/public/index.php new file mode 100644 index 0000000..43d37a2 --- /dev/null +++ b/implementation/05-http/public/index.php @@ -0,0 +1,5 @@ +pushHandler(new PrettyPageHandler()); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$request = ServerRequestFactory::fromGlobals(); +$response = new Response(); +$response->getBody()->write('Hello World!'); + +dd($response); + +/** @var string $name */ +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/06-router/.php-cs-fixer.php b/implementation/06-router/.php-cs-fixer.php new file mode 100644 index 0000000..6b8a091 --- /dev/null +++ b/implementation/06-router/.php-cs-fixer.php @@ -0,0 +1,44 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'linebreak_after_opening_tag' => true, + 'native_constant_invocation' => true, + 'native_function_invocation' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha', + 'imports_order' => [ + 'const', + 'class', + 'function', + ], + ], + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config', + ]) + ); \ No newline at end of file diff --git a/implementation/06-router/composer.json b/implementation/06-router/composer.json new file mode 100644 index 0000000..c046545 --- /dev/null +++ b/implementation/06-router/composer.json @@ -0,0 +1,31 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "scripts": { + "serve": "php -S localhost:1234 -t ./public", + "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", + "phpstan": "./vendor/bin/phpstan analyze", + "style": "./vendor/bin/php-cs-fixer fix" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "php-cs-fixer/shim": "^3.7", + "symfony/var-dumper": "^6.0" + } +} diff --git a/implementation/06-router/composer.lock b/implementation/06-router/composer.lock new file mode 100644 index 0000000..91a0551 --- /dev/null +++ b/implementation/06-router/composer.lock @@ -0,0 +1,677 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "d76b01ed13a5b0b42ce69a745090f7d9", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "php-cs-fixer/shim", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" + }, + "time": "2022-03-07T17:02:59+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.4.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.4.10" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-14T10:25:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/06-router/config/routes.php b/implementation/06-router/config/routes.php new file mode 100644 index 0000000..baea1f0 --- /dev/null +++ b/implementation/06-router/config/routes.php @@ -0,0 +1,17 @@ +addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response())->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new \Laminas\Diactoros\Response())->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}; diff --git a/implementation/06-router/phpstan-baseline.neon b/implementation/06-router/phpstan-baseline.neon new file mode 100644 index 0000000..e69de29 diff --git a/implementation/06-router/phpstan.neon b/implementation/06-router/phpstan.neon new file mode 100644 index 0000000..c2f33f3 --- /dev/null +++ b/implementation/06-router/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: 9 + paths: + - src \ No newline at end of file diff --git a/implementation/06-router/public/index.php b/implementation/06-router/public/index.php new file mode 100644 index 0000000..43d37a2 --- /dev/null +++ b/implementation/06-router/public/index.php @@ -0,0 +1,5 @@ +pushHandler(new PrettyPageHandler()); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$request = ServerRequestFactory::fromGlobals(); +$response = new Response(); + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +switch ($routeInfo[0]) { + case Dispatcher::METHOD_NOT_ALLOWED: + $response = (new Response())->withStatus(405); + $response->getBody()->write('Method not allowed'); + $response = $response->withStatus(405); + break; + case Dispatcher::FOUND: + $handler = $routeInfo[1]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + /** @var ResponseInterface $response */ + $response = call_user_func($handler, $request); + break; + case Dispatcher::NOT_FOUND: + default: + $response = (new Response())->withStatus(404); + $response->getBody()->write('Not Found!'); + break; +} + +/** @var string $name */ +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/07-dispatching-to-class/.php-cs-fixer.php b/implementation/07-dispatching-to-class/.php-cs-fixer.php new file mode 100644 index 0000000..6b8a091 --- /dev/null +++ b/implementation/07-dispatching-to-class/.php-cs-fixer.php @@ -0,0 +1,44 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'linebreak_after_opening_tag' => true, + 'native_constant_invocation' => true, + 'native_function_invocation' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha', + 'imports_order' => [ + 'const', + 'class', + 'function', + ], + ], + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config', + ]) + ); \ No newline at end of file diff --git a/implementation/07-dispatching-to-class/composer.json b/implementation/07-dispatching-to-class/composer.json new file mode 100644 index 0000000..53f35e7 --- /dev/null +++ b/implementation/07-dispatching-to-class/composer.json @@ -0,0 +1,32 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "scripts": { + "serve": "php -S localhost:1234 -t ./public", + "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", + "phpstan": "./vendor/bin/phpstan analyze", + "style": "./vendor/bin/php-cs-fixer fix" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "php-cs-fixer/shim": "^3.7", + "symfony/var-dumper": "^6.0" + } +} diff --git a/implementation/07-dispatching-to-class/composer.lock b/implementation/07-dispatching-to-class/composer.lock new file mode 100644 index 0000000..f2fa011 --- /dev/null +++ b/implementation/07-dispatching-to-class/composer.lock @@ -0,0 +1,734 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "b84022e8ec4df1e5ee958e6cbfb2307c", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "php-cs-fixer/shim", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" + }, + "time": "2022-03-07T17:02:59+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.4.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.4.10" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-14T10:25:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/07-dispatching-to-class/config/routes.php b/implementation/07-dispatching-to-class/config/routes.php new file mode 100644 index 0000000..af63e42 --- /dev/null +++ b/implementation/07-dispatching-to-class/config/routes.php @@ -0,0 +1,8 @@ +addRoute('GET', '/hello[/{name}]', [\Lubian\NoFramework\Action\Hello::class, 'handle']); + $r->addRoute('GET', '/another-route', [\Lubian\NoFramework\Action\Another::class, 'handle']); +}; diff --git a/implementation/07-dispatching-to-class/phpstan-baseline.neon b/implementation/07-dispatching-to-class/phpstan-baseline.neon new file mode 100644 index 0000000..e69de29 diff --git a/implementation/07-dispatching-to-class/phpstan.neon b/implementation/07-dispatching-to-class/phpstan.neon new file mode 100644 index 0000000..c2f33f3 --- /dev/null +++ b/implementation/07-dispatching-to-class/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: 9 + paths: + - src \ No newline at end of file diff --git a/implementation/07-dispatching-to-class/public/index.php b/implementation/07-dispatching-to-class/public/index.php new file mode 100644 index 0000000..43d37a2 --- /dev/null +++ b/implementation/07-dispatching-to-class/public/index.php @@ -0,0 +1,5 @@ +withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + } +} diff --git a/implementation/07-dispatching-to-class/src/Action/Hello.php b/implementation/07-dispatching-to-class/src/Action/Hello.php new file mode 100644 index 0000000..b516c1a --- /dev/null +++ b/implementation/07-dispatching-to-class/src/Action/Hello.php @@ -0,0 +1,21 @@ +getAttribute('name', 'Stranger'); + $response = (new Response())->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + } +} diff --git a/implementation/07-dispatching-to-class/src/Action/InternalServerError.php b/implementation/07-dispatching-to-class/src/Action/InternalServerError.php new file mode 100644 index 0000000..5f2aa5b --- /dev/null +++ b/implementation/07-dispatching-to-class/src/Action/InternalServerError.php @@ -0,0 +1,20 @@ +withStatus(500); + $response->getBody()->write('Internal Server Error.'); + return $response; + } +} diff --git a/implementation/07-dispatching-to-class/src/Action/NotAllowed.php b/implementation/07-dispatching-to-class/src/Action/NotAllowed.php new file mode 100644 index 0000000..799f4e3 --- /dev/null +++ b/implementation/07-dispatching-to-class/src/Action/NotAllowed.php @@ -0,0 +1,20 @@ +withStatus(405); + $response->getBody()->write('Method Not Allowed'); + return $response; + } +} diff --git a/implementation/07-dispatching-to-class/src/Action/NotFound.php b/implementation/07-dispatching-to-class/src/Action/NotFound.php new file mode 100644 index 0000000..0a88dc5 --- /dev/null +++ b/implementation/07-dispatching-to-class/src/Action/NotFound.php @@ -0,0 +1,20 @@ +withStatus(404); + $response->getBody()->write('Page not found'); + return $response; + } +} diff --git a/implementation/07-dispatching-to-class/src/Bootstrap.php b/implementation/07-dispatching-to-class/src/Bootstrap.php new file mode 100644 index 0000000..c13c6fb --- /dev/null +++ b/implementation/07-dispatching-to-class/src/Bootstrap.php @@ -0,0 +1,102 @@ +pushHandler(new PrettyPageHandler()); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$request = ServerRequestFactory::fromGlobals(); +$response = new Response(); + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +try { + switch ($routeInfo[0]) { + case Dispatcher::METHOD_NOT_ALLOWED: + throw new NotAllowedException(); + case Dispatcher::FOUND: + /** @var RequestHandlerInterface $handler */ + $handler = new $routeInfo[1][0](); + $method = $routeInfo[1][1]; + if (!$handler instanceof RequestHandlerInterface) { + throw new InternalServerErrorException(); + } + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->$method($request); + break; + case Dispatcher::NOT_FOUND: + default: + throw new NotFoundException(); + } +} catch (NotAllowedException) { + $response = (new NotAllowed())->handle($request); +} catch (NotFoundException) { + $response = (new NotFound())->handle($request); +} catch (Exception) { + $response = (new InternalServerError())->handle($request); +} + +/** @var string $name */ +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/07-dispatching-to-class/src/Exception/InternalServerErrorException.php b/implementation/07-dispatching-to-class/src/Exception/InternalServerErrorException.php new file mode 100644 index 0000000..aa3fddd --- /dev/null +++ b/implementation/07-dispatching-to-class/src/Exception/InternalServerErrorException.php @@ -0,0 +1,11 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'linebreak_after_opening_tag' => true, + 'native_constant_invocation' => true, + 'native_function_invocation' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha', + 'imports_order' => [ + 'const', + 'class', + 'function', + ], + ], + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config', + ]) + ); \ No newline at end of file diff --git a/implementation/08-inversion-of-control/composer.json b/implementation/08-inversion-of-control/composer.json new file mode 100644 index 0000000..53f35e7 --- /dev/null +++ b/implementation/08-inversion-of-control/composer.json @@ -0,0 +1,32 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "scripts": { + "serve": "php -S localhost:1234 -t ./public", + "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", + "phpstan": "./vendor/bin/phpstan analyze", + "style": "./vendor/bin/php-cs-fixer fix" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "php-cs-fixer/shim": "^3.7", + "symfony/var-dumper": "^6.0" + } +} diff --git a/implementation/08-inversion-of-control/composer.lock b/implementation/08-inversion-of-control/composer.lock new file mode 100644 index 0000000..f2fa011 --- /dev/null +++ b/implementation/08-inversion-of-control/composer.lock @@ -0,0 +1,734 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "b84022e8ec4df1e5ee958e6cbfb2307c", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "php-cs-fixer/shim", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" + }, + "time": "2022-03-07T17:02:59+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.4.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.4.10" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-14T10:25:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/08-inversion-of-control/config/routes.php b/implementation/08-inversion-of-control/config/routes.php new file mode 100644 index 0000000..af63e42 --- /dev/null +++ b/implementation/08-inversion-of-control/config/routes.php @@ -0,0 +1,8 @@ +addRoute('GET', '/hello[/{name}]', [\Lubian\NoFramework\Action\Hello::class, 'handle']); + $r->addRoute('GET', '/another-route', [\Lubian\NoFramework\Action\Another::class, 'handle']); +}; diff --git a/implementation/08-inversion-of-control/phpstan-baseline.neon b/implementation/08-inversion-of-control/phpstan-baseline.neon new file mode 100644 index 0000000..e69de29 diff --git a/implementation/08-inversion-of-control/phpstan.neon b/implementation/08-inversion-of-control/phpstan.neon new file mode 100644 index 0000000..c2f33f3 --- /dev/null +++ b/implementation/08-inversion-of-control/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: 9 + paths: + - src \ No newline at end of file diff --git a/implementation/08-inversion-of-control/public/index.php b/implementation/08-inversion-of-control/public/index.php new file mode 100644 index 0000000..43d37a2 --- /dev/null +++ b/implementation/08-inversion-of-control/public/index.php @@ -0,0 +1,5 @@ +response->getBody()->write('This works too!'); + return $this->response; + } +} diff --git a/implementation/08-inversion-of-control/src/Action/Hello.php b/implementation/08-inversion-of-control/src/Action/Hello.php new file mode 100644 index 0000000..b45021e --- /dev/null +++ b/implementation/08-inversion-of-control/src/Action/Hello.php @@ -0,0 +1,20 @@ +getAttribute('name', 'Stranger'); + $this->response->getBody()->write('Hello ' . $name . '!'); + return $this->response; + } +} diff --git a/implementation/08-inversion-of-control/src/Action/InternalServerError.php b/implementation/08-inversion-of-control/src/Action/InternalServerError.php new file mode 100644 index 0000000..d1acbb8 --- /dev/null +++ b/implementation/08-inversion-of-control/src/Action/InternalServerError.php @@ -0,0 +1,19 @@ +response->getBody()->write('Internal Server Error.'); + return $this->response->withStatus(500); + } +} diff --git a/implementation/08-inversion-of-control/src/Action/NotAllowed.php b/implementation/08-inversion-of-control/src/Action/NotAllowed.php new file mode 100644 index 0000000..83abb60 --- /dev/null +++ b/implementation/08-inversion-of-control/src/Action/NotAllowed.php @@ -0,0 +1,19 @@ +response->getBody()->write('Method Not Allowed'); + return $this->response->withStatus(405); + } +} diff --git a/implementation/08-inversion-of-control/src/Action/NotFound.php b/implementation/08-inversion-of-control/src/Action/NotFound.php new file mode 100644 index 0000000..ceee42f --- /dev/null +++ b/implementation/08-inversion-of-control/src/Action/NotFound.php @@ -0,0 +1,19 @@ +response->getBody()->write('Page not found'); + return $this->response->withStatus(404); + } +} diff --git a/implementation/08-inversion-of-control/src/Bootstrap.php b/implementation/08-inversion-of-control/src/Bootstrap.php new file mode 100644 index 0000000..c7ec6e8 --- /dev/null +++ b/implementation/08-inversion-of-control/src/Bootstrap.php @@ -0,0 +1,102 @@ +pushHandler(new PrettyPageHandler()); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$request = ServerRequestFactory::fromGlobals(); +$response = new Response(); + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +try { + switch ($routeInfo[0]) { + case Dispatcher::METHOD_NOT_ALLOWED: + throw new NotAllowedException(); + case Dispatcher::FOUND: + /** @var RequestHandlerInterface $handler */ + $handler = new $routeInfo[1][0]($response); + $method = $routeInfo[1][1]; + if (!$handler instanceof RequestHandlerInterface) { + throw new InternalServerErrorException(); + } + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->$method($request); + break; + case Dispatcher::NOT_FOUND: + default: + throw new NotFoundException(); + } +} catch (NotAllowedException) { + $response = (new NotAllowed($response))->handle($request); +} catch (NotFoundException) { + $response = (new NotFound($response))->handle($request); +} catch (Exception) { + $response = (new InternalServerError($response))->handle($request); +} + +/** @var string $name */ +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/08-inversion-of-control/src/Exception/InternalServerErrorException.php b/implementation/08-inversion-of-control/src/Exception/InternalServerErrorException.php new file mode 100644 index 0000000..aa3fddd --- /dev/null +++ b/implementation/08-inversion-of-control/src/Exception/InternalServerErrorException.php @@ -0,0 +1,11 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'linebreak_after_opening_tag' => true, + 'native_constant_invocation' => true, + 'native_function_invocation' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha', + 'imports_order' => [ + 'const', + 'class', + 'function', + ], + ], + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config', + ]) + ); \ No newline at end of file diff --git a/implementation/09-dependency-injector/composer.json b/implementation/09-dependency-injector/composer.json new file mode 100644 index 0000000..a38d437 --- /dev/null +++ b/implementation/09-dependency-injector/composer.json @@ -0,0 +1,34 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "scripts": { + "serve": "php -S localhost:1234 -t ./public", + "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "style": "./vendor/bin/php-cs-fixer fix" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "php-cs-fixer/shim": "^3.7", + "symfony/var-dumper": "^6.0" + } +} diff --git a/implementation/09-dependency-injector/composer.lock b/implementation/09-dependency-injector/composer.lock new file mode 100644 index 0000000..cb0aa21 --- /dev/null +++ b/implementation/09-dependency-injector/composer.lock @@ -0,0 +1,1020 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "176033a9d3cd7179cb7bb9608fa24210", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "php-cs-fixer/shim", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" + }, + "time": "2022-03-07T17:02:59+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.4.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.4.10" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-14T10:25:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/09-dependency-injector/config/dependencies.php b/implementation/09-dependency-injector/config/dependencies.php new file mode 100644 index 0000000..ac1d962 --- /dev/null +++ b/implementation/09-dependency-injector/config/dependencies.php @@ -0,0 +1,11 @@ +addDefinitions([ + \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), +]); + +return $builder->build(); diff --git a/implementation/09-dependency-injector/config/routes.php b/implementation/09-dependency-injector/config/routes.php new file mode 100644 index 0000000..af63e42 --- /dev/null +++ b/implementation/09-dependency-injector/config/routes.php @@ -0,0 +1,8 @@ +addRoute('GET', '/hello[/{name}]', [\Lubian\NoFramework\Action\Hello::class, 'handle']); + $r->addRoute('GET', '/another-route', [\Lubian\NoFramework\Action\Another::class, 'handle']); +}; diff --git a/implementation/09-dependency-injector/phpstan-baseline.neon b/implementation/09-dependency-injector/phpstan-baseline.neon new file mode 100644 index 0000000..fe0b762 --- /dev/null +++ b/implementation/09-dependency-injector/phpstan-baseline.neon @@ -0,0 +1,7 @@ +parameters: + ignoreErrors: + - + message: "#^Cannot call method handle\\(\\) on mixed\\.$#" + count: 3 + path: src/Bootstrap.php + diff --git a/implementation/09-dependency-injector/phpstan.neon b/implementation/09-dependency-injector/phpstan.neon new file mode 100644 index 0000000..28914db --- /dev/null +++ b/implementation/09-dependency-injector/phpstan.neon @@ -0,0 +1,7 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src \ No newline at end of file diff --git a/implementation/09-dependency-injector/public/index.php b/implementation/09-dependency-injector/public/index.php new file mode 100644 index 0000000..43d37a2 --- /dev/null +++ b/implementation/09-dependency-injector/public/index.php @@ -0,0 +1,5 @@ +response->getBody()->write('This works too!'); + return $this->response; + } +} diff --git a/implementation/09-dependency-injector/src/Action/Hello.php b/implementation/09-dependency-injector/src/Action/Hello.php new file mode 100644 index 0000000..b45021e --- /dev/null +++ b/implementation/09-dependency-injector/src/Action/Hello.php @@ -0,0 +1,20 @@ +getAttribute('name', 'Stranger'); + $this->response->getBody()->write('Hello ' . $name . '!'); + return $this->response; + } +} diff --git a/implementation/09-dependency-injector/src/Action/InternalServerError.php b/implementation/09-dependency-injector/src/Action/InternalServerError.php new file mode 100644 index 0000000..d1acbb8 --- /dev/null +++ b/implementation/09-dependency-injector/src/Action/InternalServerError.php @@ -0,0 +1,19 @@ +response->getBody()->write('Internal Server Error.'); + return $this->response->withStatus(500); + } +} diff --git a/implementation/09-dependency-injector/src/Action/NotAllowed.php b/implementation/09-dependency-injector/src/Action/NotAllowed.php new file mode 100644 index 0000000..83abb60 --- /dev/null +++ b/implementation/09-dependency-injector/src/Action/NotAllowed.php @@ -0,0 +1,19 @@ +response->getBody()->write('Method Not Allowed'); + return $this->response->withStatus(405); + } +} diff --git a/implementation/09-dependency-injector/src/Action/NotFound.php b/implementation/09-dependency-injector/src/Action/NotFound.php new file mode 100644 index 0000000..ceee42f --- /dev/null +++ b/implementation/09-dependency-injector/src/Action/NotFound.php @@ -0,0 +1,19 @@ +response->getBody()->write('Page not found'); + return $this->response->withStatus(404); + } +} diff --git a/implementation/09-dependency-injector/src/Bootstrap.php b/implementation/09-dependency-injector/src/Bootstrap.php new file mode 100644 index 0000000..a059b66 --- /dev/null +++ b/implementation/09-dependency-injector/src/Bootstrap.php @@ -0,0 +1,106 @@ +pushHandler(new PrettyPageHandler()); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +/** @var ContainerInterface $container */ +$container = require __DIR__ . '/../config/dependencies.php'; +/** @var ServerRequestInterface $request */ +$request = $container->get(ServerRequestInterface::class); + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +try { + switch ($routeInfo[0]) { + case Dispatcher::METHOD_NOT_ALLOWED: + throw new NotAllowedException(); + case Dispatcher::FOUND: + /** @var RequestHandlerInterface $handler */ + $handler = $container->get($routeInfo[1][0]); + $method = $routeInfo[1][1]; + if (!$handler instanceof RequestHandlerInterface) { + throw new InternalServerErrorException(); + } + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->$method($request); + break; + case Dispatcher::NOT_FOUND: + default: + throw new NotFoundException(); + } +} catch (NotAllowedException) { + $response = $container->get(NotAllowed::class)->handle($request); +} catch (NotFoundException) { + $response = $container->get(NotFound::class)->handle($request); +} catch (Exception) { + $response = $container->get(InternalServerError::class)->handle($request); +} + +/** @var string $name */ +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/09-dependency-injector/src/Exception/InternalServerErrorException.php b/implementation/09-dependency-injector/src/Exception/InternalServerErrorException.php new file mode 100644 index 0000000..aa3fddd --- /dev/null +++ b/implementation/09-dependency-injector/src/Exception/InternalServerErrorException.php @@ -0,0 +1,11 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'linebreak_after_opening_tag' => true, + 'native_constant_invocation' => true, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha', + 'imports_order' => [ + 'const', + 'class', + 'function', + ], + ], + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config', + ]) + ); \ No newline at end of file diff --git a/implementation/10-invoker/composer.json b/implementation/10-invoker/composer.json new file mode 100644 index 0000000..3c17ae5 --- /dev/null +++ b/implementation/10-invoker/composer.json @@ -0,0 +1,32 @@ +{ + "name": "lubian/no-framework", + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0" + }, + "scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "style": "./vendor/bin/php-cs-fixer fix" + } +} diff --git a/implementation/10-invoker/composer.lock b/implementation/10-invoker/composer.lock new file mode 100644 index 0000000..d300ec9 --- /dev/null +++ b/implementation/10-invoker/composer.lock @@ -0,0 +1,1020 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "c8641669dc93f9bfa8f95f20a8598451", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.4.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.4.10" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-14T10:25:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/10-invoker/config/dependencies.php b/implementation/10-invoker/config/dependencies.php new file mode 100644 index 0000000..3708e0d --- /dev/null +++ b/implementation/10-invoker/config/dependencies.php @@ -0,0 +1,12 @@ +addDefinitions([ + \Psr\Http\Message\ResponseInterface::class => fn () => new \Laminas\Diactoros\Response(), + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), + \Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), +]); + +return $builder->build(); diff --git a/implementation/10-invoker/config/routes.php b/implementation/10-invoker/config/routes.php new file mode 100644 index 0000000..a60931c --- /dev/null +++ b/implementation/10-invoker/config/routes.php @@ -0,0 +1,18 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); + $r->addRoute( + 'GET', + '/', + fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello') + ); +}; diff --git a/implementation/10-invoker/phpstan.neon b/implementation/10-invoker/phpstan.neon new file mode 100644 index 0000000..c6ce9bd --- /dev/null +++ b/implementation/10-invoker/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: 9 + paths: + - src + - config \ No newline at end of file diff --git a/implementation/10-invoker/public/favicon.ico b/implementation/10-invoker/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/10-invoker/public/index.php b/implementation/10-invoker/public/index.php new file mode 100644 index 0000000..db23804 --- /dev/null +++ b/implementation/10-invoker/public/index.php @@ -0,0 +1,6 @@ +withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + $nowString = $now->get()->format('H:i:s'); + $response->getBody()->write(' The Time is ' . $nowString); + return $response; + } +} diff --git a/implementation/10-invoker/src/Action/Other.php b/implementation/10-invoker/src/Action/Other.php new file mode 100644 index 0000000..3563983 --- /dev/null +++ b/implementation/10-invoker/src/Action/Other.php @@ -0,0 +1,20 @@ +response->withStatus(200); + $response->getBody()->write('This works too'); + return $response; + } +} diff --git a/implementation/10-invoker/src/Bootstrap.php b/implementation/10-invoker/src/Bootstrap.php new file mode 100644 index 0000000..d3bfc96 --- /dev/null +++ b/implementation/10-invoker/src/Bootstrap.php @@ -0,0 +1,110 @@ +pushHandler(new PrettyPageHandler()); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log("ERROR: " . $e->getMessage(), $e->getCode()); + echo 'AN ERROR HAPPENED!!!'; + }); +} + +$whoops->register(); + +/** @var ContainerInterface $container */ +$container = require __DIR__ . '/../config/dependencies.php'; + +/** @var InvokerInterface $invoker */ +$invoker = $container->get(InvokerInterface::class); + +/** @var ServerRequestInterface $request */ +$request = $container->get(ServerRequestInterface::class); + +/** @var ResponseInterface $response */ +$response = $container->get(ResponseInterface::class); + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri()->getPath()); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + $handler = $routeInfo[1]; + $args = $routeInfo[2]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $args['request'] = $request; + $response = $container->call($handler, $args); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed(); + case Dispatcher::NOT_FOUND: + default: + throw new NotFound(); + } +} catch (NotFound) { + $response = $response->withStatus(404); + $response->getBody()->write('Not Found'); +} catch (MethodNotAllowed) { + $response = $response->withStatus(405); + $response->getBody()->write('Method not Allowed'); +} catch (Exception $e) { + throw new InternalServerError($e->getMessage(), $e->getCode(), $e); +} + + + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase(), +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/10-invoker/src/Exception/InternalServerError.php b/implementation/10-invoker/src/Exception/InternalServerError.php new file mode 100644 index 0000000..a7b627b --- /dev/null +++ b/implementation/10-invoker/src/Exception/InternalServerError.php @@ -0,0 +1,11 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config' + ]) + ); \ No newline at end of file diff --git a/implementation/11-templating/.phpcs.xml.dist b/implementation/11-templating/.phpcs.xml.dist new file mode 100644 index 0000000..3b433f6 --- /dev/null +++ b/implementation/11-templating/.phpcs.xml.dist @@ -0,0 +1,9 @@ + + + + + src + config + + + \ No newline at end of file diff --git a/implementation/11-templating/composer.json b/implementation/11-templating/composer.json new file mode 100644 index 0000000..2c61bdd --- /dev/null +++ b/implementation/11-templating/composer.json @@ -0,0 +1,46 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3", + "mustache/mustache": "^2.14" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require-dev": { + "phpstan/phpstan": "^1.5", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0", + "squizlabs/php_codesniffer": "^3.6", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.1", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "mnapoli/hard-mode": "^0.3.0" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + } +} diff --git a/implementation/11-templating/composer.lock b/implementation/11-templating/composer.lock new file mode 100644 index 0000000..37ee56e --- /dev/null +++ b/implementation/11-templating/composer.lock @@ -0,0 +1,1550 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "e237b9a5b4210f235b1609a7ce3e065a", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "doctrine/coding-standard", + "version": "8.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/coding-standard.git", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Müller", + "email": "st.mueller@dzh-online.de" + } + ], + "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "doctrine", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/doctrine/coding-standard/issues", + "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" + }, + "time": "2021-04-03T10:54:55+00:00" + }, + { + "name": "mnapoli/hard-mode", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/hard-mode.git", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", + "shasum": "" + }, + "require": { + "doctrine/coding-standard": "^8.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Hard mode for PHP", + "support": { + "issues": "https://github.com/mnapoli/hard-mode/issues", + "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" + }, + "time": "2020-10-12T07:54:37+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.5.0" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-24T18:18:00+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^1.2.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" + }, + "time": "2021-11-18T09:30:29+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/11-templating/config/dependencies.php b/implementation/11-templating/config/dependencies.php new file mode 100644 index 0000000..c3e65a8 --- /dev/null +++ b/implementation/11-templating/config/dependencies.php @@ -0,0 +1,32 @@ +addDefinitions([ + Settings::class => fn () => require __DIR__ . '/settings.php', + ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + Mustache_Loader_FilesystemLoader::class => function (Settings $s) { + return new Mustache_Loader_FilesystemLoader( + $s->templateDir, + [ + 'extension' => $s->templateExtension, + ], + ); + }, + Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $mfl) => new Mustache_Engine(['loader' => $mfl]), +]); + +return $builder->build(); diff --git a/implementation/11-templating/config/routes.php b/implementation/11-templating/config/routes.php new file mode 100644 index 0000000..1bc00bc --- /dev/null +++ b/implementation/11-templating/config/routes.php @@ -0,0 +1,12 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); + $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/implementation/11-templating/config/settings.php b/implementation/11-templating/config/settings.php new file mode 100644 index 0000000..ad2ea0c --- /dev/null +++ b/implementation/11-templating/config/settings.php @@ -0,0 +1,9 @@ +aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/11-templating/public/index.php b/implementation/11-templating/public/index.php new file mode 100644 index 0000000..d93da3a --- /dev/null +++ b/implementation/11-templating/public/index.php @@ -0,0 +1,5 @@ +getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render('hello', $data); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/11-templating/src/Action/Other.php b/implementation/11-templating/src/Action/Other.php new file mode 100644 index 0000000..895796e --- /dev/null +++ b/implementation/11-templating/src/Action/Other.php @@ -0,0 +1,19 @@ +getBody(); + + $body->write('This works too!'); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/11-templating/src/Bootstrap.php b/implementation/11-templating/src/Bootstrap.php new file mode 100644 index 0000000..2a45e08 --- /dev/null +++ b/implementation/11-templating/src/Bootstrap.php @@ -0,0 +1,110 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$container = require __DIR__ . '/../config/dependencies.php'; +assert($container instanceof ContainerInterface); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$responseFactory = $container->get(ResponseFactory::class); +assert($responseFactory instanceof ResponseFactory); + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + $handler = $routeInfo[1]; + $vars = $routeInfo[2] ?? []; + foreach ($vars as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $vars['request'] = $request; + $invoker = $container->get(InvokerInterface::class); + assert($invoker instanceof InvokerInterface); + $response = $invoker->call($handler, $vars); + assert($response instanceof ResponseInterface); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = $responseFactory->createResponse(405); +} catch (NotFound) { + $response = $responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/11-templating/src/Exception/InternalServerError.php b/implementation/11-templating/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/11-templating/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +engine->render($template, $data); + } +} diff --git a/implementation/11-templating/src/Template/Renderer.php b/implementation/11-templating/src/Template/Renderer.php new file mode 100644 index 0000000..ff916ed --- /dev/null +++ b/implementation/11-templating/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data = []): string; +} diff --git a/implementation/11-templating/templates/hello.html b/implementation/11-templating/templates/hello.html new file mode 100644 index 0000000..0e21f2a --- /dev/null +++ b/implementation/11-templating/templates/hello.html @@ -0,0 +1,11 @@ + + + + + Hello World + + +

Hello {{name}}

+

The time is {{now}}

+ + \ No newline at end of file diff --git a/implementation/12-configuration/.php-cs-fixer.php b/implementation/12-configuration/.php-cs-fixer.php new file mode 100644 index 0000000..705a7d7 --- /dev/null +++ b/implementation/12-configuration/.php-cs-fixer.php @@ -0,0 +1,38 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config' + ]) + ); \ No newline at end of file diff --git a/implementation/12-configuration/.phpcs.xml.dist b/implementation/12-configuration/.phpcs.xml.dist new file mode 100644 index 0000000..3b433f6 --- /dev/null +++ b/implementation/12-configuration/.phpcs.xml.dist @@ -0,0 +1,9 @@ + + + + + src + config + + + \ No newline at end of file diff --git a/implementation/12-configuration/composer.json b/implementation/12-configuration/composer.json new file mode 100644 index 0000000..2c61bdd --- /dev/null +++ b/implementation/12-configuration/composer.json @@ -0,0 +1,46 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3", + "mustache/mustache": "^2.14" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require-dev": { + "phpstan/phpstan": "^1.5", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0", + "squizlabs/php_codesniffer": "^3.6", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.1", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "mnapoli/hard-mode": "^0.3.0" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + } +} diff --git a/implementation/12-configuration/composer.lock b/implementation/12-configuration/composer.lock new file mode 100644 index 0000000..37ee56e --- /dev/null +++ b/implementation/12-configuration/composer.lock @@ -0,0 +1,1550 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "e237b9a5b4210f235b1609a7ce3e065a", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "doctrine/coding-standard", + "version": "8.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/coding-standard.git", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Müller", + "email": "st.mueller@dzh-online.de" + } + ], + "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "doctrine", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/doctrine/coding-standard/issues", + "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" + }, + "time": "2021-04-03T10:54:55+00:00" + }, + { + "name": "mnapoli/hard-mode", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/hard-mode.git", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", + "shasum": "" + }, + "require": { + "doctrine/coding-standard": "^8.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Hard mode for PHP", + "support": { + "issues": "https://github.com/mnapoli/hard-mode/issues", + "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" + }, + "time": "2020-10-12T07:54:37+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.5.0" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-24T18:18:00+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^1.2.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" + }, + "time": "2021-11-18T09:30:29+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/12-configuration/config/dependencies.php b/implementation/12-configuration/config/dependencies.php new file mode 100644 index 0000000..2957edb --- /dev/null +++ b/implementation/12-configuration/config/dependencies.php @@ -0,0 +1,22 @@ + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), +]; diff --git a/implementation/12-configuration/config/routes.php b/implementation/12-configuration/config/routes.php new file mode 100644 index 0000000..1bc00bc --- /dev/null +++ b/implementation/12-configuration/config/routes.php @@ -0,0 +1,12 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); + $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/implementation/12-configuration/config/settings.php b/implementation/12-configuration/config/settings.php new file mode 100644 index 0000000..0dc42b6 --- /dev/null +++ b/implementation/12-configuration/config/settings.php @@ -0,0 +1,10 @@ +aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/12-configuration/public/index.php b/implementation/12-configuration/public/index.php new file mode 100644 index 0000000..d93da3a --- /dev/null +++ b/implementation/12-configuration/public/index.php @@ -0,0 +1,5 @@ +getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render('hello', $data); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/12-configuration/src/Action/Other.php b/implementation/12-configuration/src/Action/Other.php new file mode 100644 index 0000000..895796e --- /dev/null +++ b/implementation/12-configuration/src/Action/Other.php @@ -0,0 +1,19 @@ +getBody(); + + $body->write('This works too!'); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/12-configuration/src/Bootstrap.php b/implementation/12-configuration/src/Bootstrap.php new file mode 100644 index 0000000..f47341e --- /dev/null +++ b/implementation/12-configuration/src/Bootstrap.php @@ -0,0 +1,111 @@ +getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$responseFactory = $container->get(ResponseFactory::class); +assert($responseFactory instanceof ResponseFactory); + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + $handler = $routeInfo[1]; + $vars = $routeInfo[2] ?? []; + foreach ($vars as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $vars['request'] = $request; + $invoker = $container->get(InvokerInterface::class); + assert($invoker instanceof InvokerInterface); + $response = $invoker->call($handler, $vars); + assert($response instanceof ResponseInterface); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = $responseFactory->createResponse(405); +} catch (NotFound) { + $response = $responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/12-configuration/src/Exception/InternalServerError.php b/implementation/12-configuration/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/12-configuration/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +filePath; + } +} diff --git a/implementation/12-configuration/src/Factory/SettingsContainerProvider.php b/implementation/12-configuration/src/Factory/SettingsContainerProvider.php new file mode 100644 index 0000000..20609bf --- /dev/null +++ b/implementation/12-configuration/src/Factory/SettingsContainerProvider.php @@ -0,0 +1,25 @@ +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn () => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} diff --git a/implementation/12-configuration/src/Factory/SettingsProvider.php b/implementation/12-configuration/src/Factory/SettingsProvider.php new file mode 100644 index 0000000..ce1c5f0 --- /dev/null +++ b/implementation/12-configuration/src/Factory/SettingsProvider.php @@ -0,0 +1,10 @@ +engine->render($template, $data); + } +} diff --git a/implementation/12-configuration/src/Template/Renderer.php b/implementation/12-configuration/src/Template/Renderer.php new file mode 100644 index 0000000..ff916ed --- /dev/null +++ b/implementation/12-configuration/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data = []): string; +} diff --git a/implementation/12-configuration/templates/hello.html b/implementation/12-configuration/templates/hello.html new file mode 100644 index 0000000..0e21f2a --- /dev/null +++ b/implementation/12-configuration/templates/hello.html @@ -0,0 +1,11 @@ + + + + + Hello World + + +

Hello {{name}}

+

The time is {{now}}

+ + \ No newline at end of file diff --git a/implementation/13-refactoring/.php-cs-fixer.php b/implementation/13-refactoring/.php-cs-fixer.php new file mode 100644 index 0000000..705a7d7 --- /dev/null +++ b/implementation/13-refactoring/.php-cs-fixer.php @@ -0,0 +1,38 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config' + ]) + ); \ No newline at end of file diff --git a/implementation/13-refactoring/.phpcs.xml.dist b/implementation/13-refactoring/.phpcs.xml.dist new file mode 100644 index 0000000..3b433f6 --- /dev/null +++ b/implementation/13-refactoring/.phpcs.xml.dist @@ -0,0 +1,9 @@ + + + + + src + config + + + \ No newline at end of file diff --git a/implementation/13-refactoring/composer.json b/implementation/13-refactoring/composer.json new file mode 100644 index 0000000..92e7538 --- /dev/null +++ b/implementation/13-refactoring/composer.json @@ -0,0 +1,47 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3", + "mustache/mustache": "^2.14", + "psr/http-server-middleware": "^1.0" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require-dev": { + "phpstan/phpstan": "^1.5", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0", + "squizlabs/php_codesniffer": "^3.6", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.1", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "mnapoli/hard-mode": "^0.3.0" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + } +} diff --git a/implementation/13-refactoring/composer.lock b/implementation/13-refactoring/composer.lock new file mode 100644 index 0000000..c3bb867 --- /dev/null +++ b/implementation/13-refactoring/composer.lock @@ -0,0 +1,1607 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "eb13263f65ee72240c5f25ab82caddd0", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/master" + }, + "time": "2018-10-30T17:12:04+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "doctrine/coding-standard", + "version": "8.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/coding-standard.git", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Müller", + "email": "st.mueller@dzh-online.de" + } + ], + "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "doctrine", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/doctrine/coding-standard/issues", + "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" + }, + "time": "2021-04-03T10:54:55+00:00" + }, + { + "name": "mnapoli/hard-mode", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/hard-mode.git", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", + "shasum": "" + }, + "require": { + "doctrine/coding-standard": "^8.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Hard mode for PHP", + "support": { + "issues": "https://github.com/mnapoli/hard-mode/issues", + "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" + }, + "time": "2020-10-12T07:54:37+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.5.0" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-24T18:18:00+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^1.2.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" + }, + "time": "2021-11-18T09:30:29+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/13-refactoring/config/dependencies.php b/implementation/13-refactoring/config/dependencies.php new file mode 100644 index 0000000..c26cbe3 --- /dev/null +++ b/implementation/13-refactoring/config/dependencies.php @@ -0,0 +1,39 @@ + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, + Emitter::class => fn (BasicEmitter $e) => $e, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), + RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, +]; diff --git a/implementation/13-refactoring/config/routes.php b/implementation/13-refactoring/config/routes.php new file mode 100644 index 0000000..1bc00bc --- /dev/null +++ b/implementation/13-refactoring/config/routes.php @@ -0,0 +1,12 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); + $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/implementation/13-refactoring/config/settings.php b/implementation/13-refactoring/config/settings.php new file mode 100644 index 0000000..0dc42b6 --- /dev/null +++ b/implementation/13-refactoring/config/settings.php @@ -0,0 +1,10 @@ +aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/13-refactoring/public/index.php b/implementation/13-refactoring/public/index.php new file mode 100644 index 0000000..d93da3a --- /dev/null +++ b/implementation/13-refactoring/public/index.php @@ -0,0 +1,5 @@ +getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render('hello', $data); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/13-refactoring/src/Action/Other.php b/implementation/13-refactoring/src/Action/Other.php new file mode 100644 index 0000000..895796e --- /dev/null +++ b/implementation/13-refactoring/src/Action/Other.php @@ -0,0 +1,19 @@ +getBody(); + + $body->write('This works too!'); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/13-refactoring/src/Bootstrap.php b/implementation/13-refactoring/src/Bootstrap.php new file mode 100644 index 0000000..2a3cc85 --- /dev/null +++ b/implementation/13-refactoring/src/Bootstrap.php @@ -0,0 +1,40 @@ +getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); diff --git a/implementation/13-refactoring/src/Exception/InternalServerError.php b/implementation/13-refactoring/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/13-refactoring/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +factory::fromGlobals(); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} diff --git a/implementation/13-refactoring/src/Factory/FileSystemSettingsProvider.php b/implementation/13-refactoring/src/Factory/FileSystemSettingsProvider.php new file mode 100644 index 0000000..3a847ec --- /dev/null +++ b/implementation/13-refactoring/src/Factory/FileSystemSettingsProvider.php @@ -0,0 +1,18 @@ +filePath; + } +} diff --git a/implementation/13-refactoring/src/Factory/RequestFactory.php b/implementation/13-refactoring/src/Factory/RequestFactory.php new file mode 100644 index 0000000..2b17abc --- /dev/null +++ b/implementation/13-refactoring/src/Factory/RequestFactory.php @@ -0,0 +1,11 @@ +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn () => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} diff --git a/implementation/13-refactoring/src/Factory/SettingsProvider.php b/implementation/13-refactoring/src/Factory/SettingsProvider.php new file mode 100644 index 0000000..ce1c5f0 --- /dev/null +++ b/implementation/13-refactoring/src/Factory/SettingsProvider.php @@ -0,0 +1,10 @@ +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + + echo $response->getBody(); + } +} diff --git a/implementation/13-refactoring/src/Http/Emitter.php b/implementation/13-refactoring/src/Http/Emitter.php new file mode 100644 index 0000000..ce4c035 --- /dev/null +++ b/implementation/13-refactoring/src/Http/Emitter.php @@ -0,0 +1,10 @@ +getAttribute($this->routeAttributeName, false); + assert($handler !== false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} diff --git a/implementation/13-refactoring/src/Http/RouteMiddleware.php b/implementation/13-refactoring/src/Http/RouteMiddleware.php new file mode 100644 index 0000000..e3df6f8 --- /dev/null +++ b/implementation/13-refactoring/src/Http/RouteMiddleware.php @@ -0,0 +1,69 @@ +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} diff --git a/implementation/13-refactoring/src/Http/RoutedRequestHandler.php b/implementation/13-refactoring/src/Http/RoutedRequestHandler.php new file mode 100644 index 0000000..a7407c9 --- /dev/null +++ b/implementation/13-refactoring/src/Http/RoutedRequestHandler.php @@ -0,0 +1,10 @@ +routeMiddleware->process($request, $this->handler); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} diff --git a/implementation/13-refactoring/src/Service/Time/Now.php b/implementation/13-refactoring/src/Service/Time/Now.php new file mode 100644 index 0000000..79224b3 --- /dev/null +++ b/implementation/13-refactoring/src/Service/Time/Now.php @@ -0,0 +1,10 @@ +engine->render($template, $data); + } +} diff --git a/implementation/13-refactoring/src/Template/Renderer.php b/implementation/13-refactoring/src/Template/Renderer.php new file mode 100644 index 0000000..ff916ed --- /dev/null +++ b/implementation/13-refactoring/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data = []): string; +} diff --git a/implementation/13-refactoring/templates/hello.html b/implementation/13-refactoring/templates/hello.html new file mode 100644 index 0000000..0e21f2a --- /dev/null +++ b/implementation/13-refactoring/templates/hello.html @@ -0,0 +1,11 @@ + + + + + Hello World + + +

Hello {{name}}

+

The time is {{now}}

+ + \ No newline at end of file From bb139509665420eaddffbae65fa1d763016f74c8 Mon Sep 17 00:00:00 2001 From: lubiana Date: Thu, 31 Mar 2022 08:34:30 +0200 Subject: [PATCH 276/314] asdf --- 14-middleware.md | 142 +- app/.php-cs-fixer.php | 38 + app/.phpcs.xml.dist | 9 + app/composer.json | 52 + app/composer.lock | 2308 +++++++++++++++++ app/config/dependencies.php | 51 + app/config/middlewares.php | 12 + app/config/routes.php | 14 + app/config/settings.php | 12 + app/data/pages/01-front-controller.md | 53 + app/data/pages/02-composer.md | 75 + app/data/pages/03-error-handler.md | 79 + app/data/pages/04-development-helpers.md | 260 ++ app/data/pages/05-http.md | 124 + app/data/pages/06-router.md | 101 + app/data/pages/07-dispatching-to-a-class.md | 137 + app/data/pages/08-inversion-of-control.md | 54 + app/data/pages/09-dependency-injector.md | 213 ++ app/data/pages/10-invoker.md | 102 + app/data/pages/11-templating.md | 240 ++ app/data/pages/12-configuration.md | 201 ++ app/data/pages/13-refactoring.md | 377 +++ app/data/pages/14-middleware.md | 298 +++ app/phpstan-baseline.neon | 7 + app/phpstan.neon | 8 + app/public/css/spectre-exp.min.css | 1 + app/public/css/spectre-icons.min.css | 1 + app/public/css/spectre.min.css | 1 + app/public/index.php | 5 + app/src/Action/Hello.php | 31 + app/src/Action/Other.php | 19 + app/src/Action/Page.php | 35 + app/src/Bootstrap.php | 40 + app/src/Exception/InternalServerError.php | 9 + app/src/Exception/MethodNotAllowed.php | 9 + app/src/Exception/NotFound.php | 9 + app/src/Factory/ContainerProvider.php | 10 + app/src/Factory/DiactorosRequestFactory.php | 28 + .../Factory/FileSystemSettingsProvider.php | 22 + app/src/Factory/PipelineProvider.php | 25 + app/src/Factory/RequestFactory.php | 11 + app/src/Factory/SettingsContainerProvider.php | 25 + app/src/Factory/SettingsProvider.php | 10 + app/src/Http/AddRoute.php | 17 - app/src/Http/BasicEmitter.php | 38 + app/src/Http/ContainerPipeline.php | 82 + app/src/Http/Emitter.php | 10 + app/src/Http/InvokerRoutedHandler.php | 34 + app/src/Http/Pipeline.php | 11 + app/src/Http/RouteDecorationMiddleware.php | 97 - app/src/Http/RouteMiddleware.php | 69 + app/src/Http/RoutedRequestHandler.php | 10 + app/src/Kernel.php | 49 +- app/src/Middleware/CacheMiddleware.php | 33 + app/src/Model/MarkdownPage.php | 13 + app/src/Repository/CachedMarkdownPageRepo.php | 46 + app/src/Repository/MarkdownPageFilesystem.php | 63 + app/src/Repository/MarkdownPageRepo.php | 17 + app/src/Service/Time/Now.php | 10 + app/src/Service/Time/SystemClockNow.php | 13 + app/src/Settings.php | 16 + app/src/Template/MustacheRenderer.php | 17 + app/src/Template/Renderer.php | 11 + app/templates/hello.html | 6 + app/templates/page.html | 5 + app/templates/partials/foot.html | 3 + app/templates/partials/head.html | 11 + .../14-middleware/.php-cs-fixer.php | 38 + implementation/14-middleware/.phpcs.xml.dist | 9 + implementation/14-middleware/composer.json | 50 + implementation/14-middleware/composer.lock | 1815 +++++++++++++ .../14-middleware/config/dependencies.php | 42 + .../14-middleware/config/middlewares.php | 11 + .../14-middleware/config/routes.php | 12 + .../14-middleware/config/settings.php | 11 + .../14-middleware/phpstan-baseline.neon | 7 + implementation/14-middleware/phpstan.neon | 8 + .../14-middleware/public/favicon.ico | Bin 0 -> 15086 bytes implementation/14-middleware/public/index.php | 5 + implementation/14-middleware/src/.gitkeep | 0 .../14-middleware/src/Action/Hello.php | 31 + .../14-middleware/src/Action/Other.php | 19 + .../14-middleware/src/Bootstrap.php | 40 + .../src/Exception/InternalServerError.php | 9 + .../src/Exception/MethodNotAllowed.php | 9 + .../14-middleware/src/Exception/NotFound.php | 9 + .../src/Factory/ContainerProvider.php | 10 + .../src/Factory/DiactorosRequestFactory.php | 28 + .../Factory/FileSystemSettingsProvider.php | 22 + .../src/Factory/PipelineProvider.php | 25 + .../src/Factory/RequestFactory.php | 11 + .../src/Factory/SettingsContainerProvider.php | 25 + .../src/Factory/SettingsProvider.php | 10 + .../14-middleware/src/Http/BasicEmitter.php | 38 + .../src/Http/ContainerPipeline.php | 82 + .../14-middleware/src/Http/Emitter.php | 10 + .../src/Http/InvokerRoutedHandler.php | 34 + .../14-middleware/src/Http/Pipeline.php | 11 + .../src/Http/RouteMiddleware.php | 69 + .../src/Http/RoutedRequestHandler.php | 10 + implementation/14-middleware/src/Kernel.php | 32 + .../14-middleware/src/Service/Time/Now.php | 10 + .../src/Service/Time/SystemClockNow.php | 13 + implementation/14-middleware/src/Settings.php | 15 + .../src/Template/MustacheRenderer.php | 17 + .../14-middleware/src/Template/Renderer.php | 11 + .../14-middleware/templates/hello.html | 11 + 107 files changed, 8372 insertions(+), 186 deletions(-) create mode 100644 app/.php-cs-fixer.php create mode 100644 app/.phpcs.xml.dist create mode 100644 app/composer.json create mode 100644 app/composer.lock create mode 100644 app/config/dependencies.php create mode 100644 app/config/middlewares.php create mode 100644 app/config/routes.php create mode 100644 app/config/settings.php create mode 100644 app/data/pages/01-front-controller.md create mode 100644 app/data/pages/02-composer.md create mode 100644 app/data/pages/03-error-handler.md create mode 100644 app/data/pages/04-development-helpers.md create mode 100644 app/data/pages/05-http.md create mode 100644 app/data/pages/06-router.md create mode 100644 app/data/pages/07-dispatching-to-a-class.md create mode 100644 app/data/pages/08-inversion-of-control.md create mode 100644 app/data/pages/09-dependency-injector.md create mode 100644 app/data/pages/10-invoker.md create mode 100644 app/data/pages/11-templating.md create mode 100644 app/data/pages/12-configuration.md create mode 100644 app/data/pages/13-refactoring.md create mode 100644 app/data/pages/14-middleware.md create mode 100644 app/phpstan-baseline.neon create mode 100644 app/phpstan.neon create mode 100644 app/public/css/spectre-exp.min.css create mode 100644 app/public/css/spectre-icons.min.css create mode 100644 app/public/css/spectre.min.css create mode 100644 app/public/index.php create mode 100644 app/src/Action/Hello.php create mode 100644 app/src/Action/Other.php create mode 100644 app/src/Action/Page.php create mode 100644 app/src/Bootstrap.php create mode 100644 app/src/Exception/InternalServerError.php create mode 100644 app/src/Exception/MethodNotAllowed.php create mode 100644 app/src/Exception/NotFound.php create mode 100644 app/src/Factory/ContainerProvider.php create mode 100644 app/src/Factory/DiactorosRequestFactory.php create mode 100644 app/src/Factory/FileSystemSettingsProvider.php create mode 100644 app/src/Factory/PipelineProvider.php create mode 100644 app/src/Factory/RequestFactory.php create mode 100644 app/src/Factory/SettingsContainerProvider.php create mode 100644 app/src/Factory/SettingsProvider.php delete mode 100644 app/src/Http/AddRoute.php create mode 100644 app/src/Http/BasicEmitter.php create mode 100644 app/src/Http/ContainerPipeline.php create mode 100644 app/src/Http/Emitter.php create mode 100644 app/src/Http/InvokerRoutedHandler.php create mode 100644 app/src/Http/Pipeline.php delete mode 100644 app/src/Http/RouteDecorationMiddleware.php create mode 100644 app/src/Http/RouteMiddleware.php create mode 100644 app/src/Http/RoutedRequestHandler.php create mode 100644 app/src/Middleware/CacheMiddleware.php create mode 100644 app/src/Model/MarkdownPage.php create mode 100644 app/src/Repository/CachedMarkdownPageRepo.php create mode 100644 app/src/Repository/MarkdownPageFilesystem.php create mode 100644 app/src/Repository/MarkdownPageRepo.php create mode 100644 app/src/Service/Time/Now.php create mode 100644 app/src/Service/Time/SystemClockNow.php create mode 100644 app/src/Settings.php create mode 100644 app/src/Template/MustacheRenderer.php create mode 100644 app/src/Template/Renderer.php create mode 100644 app/templates/hello.html create mode 100644 app/templates/page.html create mode 100644 app/templates/partials/foot.html create mode 100644 app/templates/partials/head.html create mode 100644 implementation/14-middleware/.php-cs-fixer.php create mode 100644 implementation/14-middleware/.phpcs.xml.dist create mode 100644 implementation/14-middleware/composer.json create mode 100644 implementation/14-middleware/composer.lock create mode 100644 implementation/14-middleware/config/dependencies.php create mode 100644 implementation/14-middleware/config/middlewares.php create mode 100644 implementation/14-middleware/config/routes.php create mode 100644 implementation/14-middleware/config/settings.php create mode 100644 implementation/14-middleware/phpstan-baseline.neon create mode 100644 implementation/14-middleware/phpstan.neon create mode 100644 implementation/14-middleware/public/favicon.ico create mode 100644 implementation/14-middleware/public/index.php create mode 100644 implementation/14-middleware/src/.gitkeep create mode 100644 implementation/14-middleware/src/Action/Hello.php create mode 100644 implementation/14-middleware/src/Action/Other.php create mode 100644 implementation/14-middleware/src/Bootstrap.php create mode 100644 implementation/14-middleware/src/Exception/InternalServerError.php create mode 100644 implementation/14-middleware/src/Exception/MethodNotAllowed.php create mode 100644 implementation/14-middleware/src/Exception/NotFound.php create mode 100644 implementation/14-middleware/src/Factory/ContainerProvider.php create mode 100644 implementation/14-middleware/src/Factory/DiactorosRequestFactory.php create mode 100644 implementation/14-middleware/src/Factory/FileSystemSettingsProvider.php create mode 100644 implementation/14-middleware/src/Factory/PipelineProvider.php create mode 100644 implementation/14-middleware/src/Factory/RequestFactory.php create mode 100644 implementation/14-middleware/src/Factory/SettingsContainerProvider.php create mode 100644 implementation/14-middleware/src/Factory/SettingsProvider.php create mode 100644 implementation/14-middleware/src/Http/BasicEmitter.php create mode 100644 implementation/14-middleware/src/Http/ContainerPipeline.php create mode 100644 implementation/14-middleware/src/Http/Emitter.php create mode 100644 implementation/14-middleware/src/Http/InvokerRoutedHandler.php create mode 100644 implementation/14-middleware/src/Http/Pipeline.php create mode 100644 implementation/14-middleware/src/Http/RouteMiddleware.php create mode 100644 implementation/14-middleware/src/Http/RoutedRequestHandler.php create mode 100644 implementation/14-middleware/src/Kernel.php create mode 100644 implementation/14-middleware/src/Service/Time/Now.php create mode 100644 implementation/14-middleware/src/Service/Time/SystemClockNow.php create mode 100644 implementation/14-middleware/src/Settings.php create mode 100644 implementation/14-middleware/src/Template/MustacheRenderer.php create mode 100644 implementation/14-middleware/src/Template/Renderer.php create mode 100644 implementation/14-middleware/templates/hello.html diff --git a/14-middleware.md b/14-middleware.md index f4d3f3b..e698327 100644 --- a/14-middleware.md +++ b/14-middleware.md @@ -63,7 +63,7 @@ final class CachingMiddleware implements MiddlewareInterface public function __construct(private CacheInterface $cache){} public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { - if ($request->getAttribute('isAuthenticated', false)) { + if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { $key = $request->getUri()->getPath(); return $this->cache->get($key, fn() => $handler->handle($request), 10); } @@ -135,16 +135,33 @@ interface Pipeline And our implementation looks something like this: ```php -final class SimplePipeline implements Pipeline + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ public function __construct( - private readonly array $middlewares, - private RequestHandlerInterface $tip - ){} + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } public function dispatch(ServerRequestInterface $request): ResponseInterface { @@ -156,19 +173,49 @@ final class SimplePipeline implements Pipeline { foreach (array_reverse($this->middlewares) as $middleware) { $next = $this->tip; - $this->tip = new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $next - ){} - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->next); - } - }; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } } } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } } ``` @@ -180,27 +227,58 @@ and store that itself as the current tip. There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) -Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline: +Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline +Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline + ```php - Pipeline::class => function ( - RouteDecoratedRequestHandler $tip, - RouteDecorationMiddleware $router, - ) { - $middlewares = require __DIR__ . '/middlewares.php'; - $middlewares[] = $router; - return new SimplePipeline($middlewares, $tip); - }, +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} +``` + +And configure the container to use the Factory to create the Pipeline: + +```php + ..., + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + ... ``` And of course a new file called middlewares.php in our config folder: ```php -setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config' + ]) + ); \ No newline at end of file diff --git a/app/.phpcs.xml.dist b/app/.phpcs.xml.dist new file mode 100644 index 0000000..3b433f6 --- /dev/null +++ b/app/.phpcs.xml.dist @@ -0,0 +1,9 @@ + + + + + src + config + + + \ No newline at end of file diff --git a/app/composer.json b/app/composer.json new file mode 100644 index 0000000..9cbc356 --- /dev/null +++ b/app/composer.json @@ -0,0 +1,52 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3", + "mustache/mustache": "^2.14", + "psr/http-server-middleware": "^1.0", + "middlewares/trailing-slash": "^2.0", + "middlewares/whoops": "^2.0", + "erusev/parsedown": "^1.7", + "symfony/cache": "^6.0" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require-dev": { + "phpstan/phpstan": "^1.5", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0", + "squizlabs/php_codesniffer": "^3.6", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.1", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "mnapoli/hard-mode": "^0.3.0", + "psalm/phar": "^4.22" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + } +} diff --git a/app/composer.lock b/app/composer.lock new file mode 100644 index 0000000..b10158c --- /dev/null +++ b/app/composer.lock @@ -0,0 +1,2308 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "0f81a412e25ca1269493dface2804540", + "packages": [ + { + "name": "erusev/parsedown", + "version": "1.7.4", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "support": { + "issues": "https://github.com/erusev/parsedown/issues", + "source": "https://github.com/erusev/parsedown/tree/1.7.x" + }, + "time": "2019-12-30T22:54:17+00:00" + }, + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "middlewares/trailing-slash", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/middlewares/trailing-slash.git", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", + "shasum": "" + }, + "require": { + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to normalize the trailing slash of the uri path", + "homepage": "https://github.com/middlewares/trailing-slash", + "keywords": [ + "http", + "middleware", + "normalize", + "path", + "psr-15", + "psr-7", + "slash" + ], + "support": { + "issues": "https://github.com/middlewares/trailing-slash/issues", + "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" + }, + "time": "2020-12-02T00:06:55+00:00" + }, + { + "name": "middlewares/utils", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/middlewares/utils.git", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v2.16", + "guzzlehttp/psr7": "^2.0", + "laminas/laminas-diactoros": "^2.4", + "nyholm/psr7": "^1.0", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "slim/psr7": "^1.4", + "squizlabs/php_codesniffer": "^3.5", + "sunrise/http-message": "^1.0", + "sunrise/http-server-request": "^1.0", + "sunrise/stream": "^1.0.15", + "sunrise/uri": "^1.0.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\Utils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Common utils for PSR-15 middleware packages", + "homepage": "https://github.com/middlewares/utils", + "keywords": [ + "PSR-11", + "http", + "middleware", + "psr-15", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/middlewares/utils/issues", + "source": "https://github.com/middlewares/utils/tree/v3.3.0" + }, + "time": "2021-07-04T17:56:23+00:00" + }, + { + "name": "middlewares/whoops", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/middlewares/whoops.git", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.5", + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^5.0 || ^7.0", + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to use Whoops as error handler", + "homepage": "https://github.com/middlewares/whoops", + "keywords": [ + "error", + "http", + "middleware", + "psr-15", + "psr-7", + "server", + "whoops" + ], + "support": { + "issues": "https://github.com/middlewares/whoops/issues", + "source": "https://github.com/middlewares/whoops/tree/v2.0.2" + }, + "time": "2022-01-27T20:31:30+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/master" + }, + "time": "2018-10-30T17:12:04+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "symfony/cache", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", + "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^1.1.7|^2|^3", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/var-exporter": "^5.4|^6.0" + }, + "conflict": { + "doctrine/dbal": "<2.13.1", + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/var-dumper": "<5.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^2.13.1|^3.0", + "predis/predis": "^1.1", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/2f7463f156cf9c665d9317e21a809c3bbff5754e", + "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "psr/cache": "^3.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.0.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-08-17T15:35:52+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-07-12T14:48:14+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-04T16:48:04+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "130229a482abf17635a685590958894dfb4b4360" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/130229a482abf17635a685590958894dfb4b4360", + "reference": "130229a482abf17635a685590958894dfb4b4360", + "shasum": "" + }, + "require": { + "php": ">=8.0.2" + }, + "require-dev": { + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "doctrine/coding-standard", + "version": "8.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/coding-standard.git", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Müller", + "email": "st.mueller@dzh-online.de" + } + ], + "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "doctrine", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/doctrine/coding-standard/issues", + "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" + }, + "time": "2021-04-03T10:54:55+00:00" + }, + { + "name": "mnapoli/hard-mode", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/hard-mode.git", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", + "shasum": "" + }, + "require": { + "doctrine/coding-standard": "^8.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Hard mode for PHP", + "support": { + "issues": "https://github.com/mnapoli/hard-mode/issues", + "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" + }, + "time": "2020-10-12T07:54:37+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.5.0" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-24T18:18:00+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^1.2.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" + }, + "time": "2021-11-18T09:30:29+00:00" + }, + { + "name": "psalm/phar", + "version": "4.22.0", + "source": { + "type": "git", + "url": "https://github.com/psalm/phar.git", + "reference": "feebed09c9782d9aaa819b794d880c2671ba0e4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/psalm/phar/zipball/feebed09c9782d9aaa819b794d880c2671ba0e4c", + "reference": "feebed09c9782d9aaa819b794d880c2671ba0e4c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "vimeo/psalm": "*" + }, + "bin": [ + "psalm.phar" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer-based Psalm Phar", + "support": { + "issues": "https://github.com/psalm/phar/issues", + "source": "https://github.com/psalm/phar/tree/4.22.0" + }, + "time": "2022-02-27T11:01:37+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/app/config/dependencies.php b/app/config/dependencies.php new file mode 100644 index 0000000..e1ea3bf --- /dev/null +++ b/app/config/dependencies.php @@ -0,0 +1,51 @@ + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, + Emitter::class => fn (BasicEmitter $e) => $e, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + CacheInterface::class => fn (FilesystemAdapter $a) => $a, + MarkdownPageFilesystem::class => fn (Settings $s) => new MarkdownPageFilesystem($s->pagesPath), + CachedMarkdownPageRepo::class => fn (CacheInterface $c, MarkdownPageFilesystem $r) => new CachedMarkdownPageRepo($c, $r), + MarkdownPageRepo::class => fn (MarkdownPageFilesystem $r) => $r, +]; diff --git a/app/config/middlewares.php b/app/config/middlewares.php new file mode 100644 index 0000000..891cc83 --- /dev/null +++ b/app/config/middlewares.php @@ -0,0 +1,12 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/page/{page}', Page::class); + $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); + $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/app/config/settings.php b/app/config/settings.php new file mode 100644 index 0000000..8a0861d --- /dev/null +++ b/app/config/settings.php @@ -0,0 +1,12 @@ +>](02-composer.md) + +### Front Controller + +A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. + +To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. + +A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. + +The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. + +But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. + +So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. + +Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: + +```php +>](02-composer.md) diff --git a/app/data/pages/02-composer.md b/app/data/pages/02-composer.md new file mode 100644 index 0000000..a25a4a8 --- /dev/null +++ b/app/data/pages/02-composer.md @@ -0,0 +1,75 @@ +[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) + +### Composer + +[Composer](https://getcomposer.org/) is a dependency manager for PHP. + +Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do +something. With Composer, you can install third-party libraries for your application. + +If you don't have Composer installed already, head over to the website and install it. You can find Composer packages +for your project on [Packagist](https://packagist.org/). + +Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will +be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. + +Add the following content to the file: + +```json +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubiana", + "email": "lubiana@hannover.ccc.de" + } + ] +} +``` + +In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use +whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. +Just replace it with your namespace in your own code. + +I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. + +As the Bootstrap.php file is placed in that directory we should +add the namespace to the File as well. Here is my current Bootstrap.php +as a reference: + +```php +>](03-error-handler.md) diff --git a/app/data/pages/03-error-handler.md b/app/data/pages/03-error-handler.md new file mode 100644 index 0000000..60465d0 --- /dev/null +++ b/app/data/pages/03-error-handler.md @@ -0,0 +1,79 @@ +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) + +### Error Handler + +An error handler allows you to customize what happens if your code results in an error. + +A nice error page with a lot of information for debugging goes a long way during development. So the first package +for your application will take care of that. + +I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. +If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, +you have total control over your project. + +An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) + +To install a new package, open up your `composer.json` and add the package to the require part. It should now look +like this: + +```php +"require": { + "php": ">=8.1.0", + "filp/whoops": "^2.14" +}, +``` + +Now run `composer update` in your console and it will be installed. + +Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, +i that case composer automatically installs the package and updates your composer.json-file. + +But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, +ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you +only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. + +**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message +can help someone to gain access to your system. Always show a user friendly error page instead and send an email to +yourself, write to a log or something similar. So only you can see the errors in the production environment. + +For development that does not make sense though -- you want a nice error page. The solution is to have an environment +switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in +case no environment has been set. + +Then after the error handler registration, throw an `Exception` to test if everything is working correctly. +Your `Bootstrap.php` should now look similar to this: + +```php +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (\Throwable $e) { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +throw new \Exception("Ooooopsie"); + +``` + +You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until +you get it working. Now would also be a good time for another commit. + + +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/app/data/pages/04-development-helpers.md b/app/data/pages/04-development-helpers.md new file mode 100644 index 0000000..74f913c --- /dev/null +++ b/app/data/pages/04-development-helpers.md @@ -0,0 +1,260 @@ +[<< previous](03-error-handler.md) | [next >>](05-http.md) + +### Development Helpers + +I have added some more helpers to my composer.json that help me with development. As these are scripts and programms +used only for development they should not be used in a production environment. Composer has a specific sections in its +file called "dev-dependencies", everything that is required in this section does not get installen in production. + +Let's install our dev-helpers and i will explain them one by one: +`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` + +#### Static Code Analysis with phpstan + +Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or +create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements +that are not (or not always) available, if-statements that always are true and a lot of other stuff. + +A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. + +```php +/** + * @param \DateTime $date + * @return void + */ +function printDate($date) { + $date->format('Y-m-d H:i:s'); +} + +printDate('now'); +``` +if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` + +It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has +no use, and secondly, that we are calling the function with a string instead of a datetime object. + +```shell +1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + ------ --------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ --------------------------------------------------------------------------------------------- +30 Call to method DateTime::format() on a separate line has no effect. +33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. + ------ --------------------------------------------------------------------------------------------- +``` + +The second error is something that "declare strict-types" already catches for us, but the first error is something that +we usually would not discover easily without speccially looking for this errortype. + +We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and +path everytime we want to check our code for errors: + +```yaml +parameters: + level: max + paths: + - src +``` +now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project + +With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us +some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. + +```shell +composer require --dev phpstan/extension-installer +composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules +``` + +During the first install you need to allow the extension installer to actually install the extension. The second command +installs some more strict rulesets and activates them in phpstan. + +If we now rerun phpstan it already tells us about some errors we have made: + +``` + ------ ----------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ ----------------------------------------------------------------------------------------------- +10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider + using long ternary. +25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: + http://bit.ly/subtypeexception +26 Unreachable statement - code above always terminates. + ------ ----------------------------------------------------------------------------------------------- +``` + +The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove +that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific +problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working +on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages +everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we +are adding to our code. + +In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the +phpstan.neon file and run phpstan with the +'--generate-baseline' option: + +```yaml +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` +```shell +[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline +Note: Using configuration file /home/vagrant/app/phpstan.neon. + 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + + + [OK] Baseline generated with 1 error. + + +``` + +you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) + +#### PHP-CS-Fixer + +Another great tool is the php-cs-fixer, which just applies a specific style to your code. + +when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current +directory. + +You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) + +personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup +a configuration file that i use in all my projects .php-cs-fixer.php: + +```php +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + ]) + ); +``` + +#### PHP Codesniffer + +The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra +rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. + +it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. + +Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have +guessed we are adding some extra rules. + +Lets install the ruleset with composer +```shell +composer require --dev mnapoli/hard-mode +``` + +and add a configuration file to actually use it '.phpcs.xml.dist' +```xml + + + + + src + + + +``` + +running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. + +``` +[vagrant@archlinux app]$ ./vendor/bin/phpcs + +FILE: src/Bootstrap.php +---------------------------------------------------------------------------------------------------- +FOUND 4 ERRORS AFFECTING 4 LINES +---------------------------------------------------------------------------------------------------- + 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. + 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead +---------------------------------------------------------------------------------------------------- +PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY +---------------------------------------------------------------------------------------------------- + +Time: 639ms; Memory: 10MB +``` + +You can then use `./vendor/bin/phpcbf` to try to fix them + + +#### Symfony Var-Dumper + +another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small +functions. + +dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects +or arrays. + +dd() on the other hand is a function that dumps its parameters and then exits the php-script. + +you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. + +#### Composer scripts + +now we have a few commands that are available on the command line. i personally do not like to type complex commands +with lots of parameters by hand all the time, so i added a few lines to my composer.json: + +```json +"scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" +}, +``` + +that way i can just type "composer" followed by the command name in the root of my project. if i want to start the +php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the +time. + +You could also configure PhpStorm to automatically run these commands in the background and highlight the violations +directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my +flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. + +My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before +commiting and pushing the code. + +[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/app/data/pages/05-http.md b/app/data/pages/05-http.md new file mode 100644 index 0000000..6166214 --- /dev/null +++ b/app/data/pages/05-http.md @@ -0,0 +1,124 @@ +[<< previous](04-development-helpers.md) | [next >>](06-router.md) + +### HTTP + +PHP already has a few things built in to make working with HTTP easier. For example there are the +[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. + +These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, +if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, +then you will want a class with a nice object-oriented interface that you can use in your application instead. + +Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The +standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php +projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. + +As this is a widely adopted standard there are already several implementations available for us to use. I will choose +the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. + +Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a +[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. + +Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use +that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding +the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). + + +to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit +enter + +Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): + +```php +$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); +$response = new \Laminas\Diactoros\Response; +$response->getBody()->write('Hello World! '); +$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); +``` + +This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. + +In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the +write()-Method on that object. + + +To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: + +```php +echo $response->getBody(); +``` + +This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only +stores data. + +You can play around with the other methods of the Request object and take a look at its content with the dd() function. + +```php +dd($response) +``` + +Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot +be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which +creates a copy of the request object with the changed property and returns that clone: + +```php +$response = $response->withStatus(200); +$response = $response->withAddedHeader('Content-type', 'application/json'); +``` + +If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been +defined this way. + +But if you have been keeping attention you might argue that the following line should not work if the request object is +immutable. + +```php +$response->getBody()->write('Hello World!'); +``` + +The response-body implements a stream interface which is immutable for some reasons that are described in the +[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be +aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in +the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. +I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content +to a response object. + +```php +$body = $response->getBody(); +$body->write('Hello World!'); +$response = $response->withBody($body); +``` + +Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our +output-logic a little bit more. Replace the line that echos the response-body with the following: + +```php +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); +``` + +This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a +webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. + +Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. + +Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter + + +[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/app/data/pages/06-router.md b/app/data/pages/06-router.md new file mode 100644 index 0000000..6c39ae5 --- /dev/null +++ b/app/data/pages/06-router.md @@ -0,0 +1,101 @@ +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) + +### Router + +A router dispatches to different handlers depending on rules that you have set up. + +With your current setup it does not matter what URL is used to access the application, it will always result in the same +response. So let's fix that now. + +I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own +favorite package. + +Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) + +By now you know how to install Composer packages, so I will leave that to you. + +Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. + +```php +$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +switch ($routeInfo[0]) { + case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: + $response = (new \Laminas\Diactoros\Response)->withStatus(405); + $response->getBody()->write('Method not allowed'); + $response = $response->withStatus(405); + break; + case \FastRoute\Dispatcher::FOUND: + $handler = $routeInfo[1]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + /** @var \Psr\Http\Message\ResponseInterface $response */ + $response = call_user_func($handler, $request); + break; + case \FastRoute\Dispatcher::NOT_FOUND: + default: + $response = (new \Laminas\Diactoros\Response)->withStatus(404); + $response->getBody()->write('Not Found!'); + break; +} +``` + +In the first part of the code, you are registering the available routes for your application. In the second part, the +dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, +we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. +If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. + +This setup might work for really small applications, but once you start adding a few routes your bootstrap file will +quickly get cluttered. So let's move them out into a separate file. + +Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; + +```php +addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}; +``` + +Now let's rewrite the route dispatcher part to use the `Routes.php` file. + +```php +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); +``` + +This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. + +Of course we now need to add the 'config' folder to the configuration files of our +devhelpers so that they can scan that directory as well. + +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/app/data/pages/07-dispatching-to-a-class.md b/app/data/pages/07-dispatching-to-a-class.md new file mode 100644 index 0000000..0c961a4 --- /dev/null +++ b/app/data/pages/07-dispatching-to-a-class.md @@ -0,0 +1,137 @@ +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) + +### Dispatching to a Class + +In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). +MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to +learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) +and the followup posts. + +So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). + +We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other +common names are 'Controllers' or 'Actions'. + +Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. +In there, create a `Hello.php` file. + +```php +getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + } +} +``` + +You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) +that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is +fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement +it in some other parts of our application as well. In order to use that Interface we have to require it with composer: +'composer require psr/http-server-handler'. + +The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. +At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. + +Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: + +```php +return function(\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); + $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); +}; +``` + +Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared +the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing +the response to the one we defined for the second route. + +To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: + +```php +case \FastRoute\Dispatcher::FOUND: + $handler = new $routeInfo[1]; + if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { + throw new \Exception('Invalid Requesthandler'); + } + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + assert($response instanceof \Psr\Http\Message\ResponseInterface) + break; +``` + +So instead of just calling a method you are now instantiating an object and then calling the method on it. + +Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. + +And of course don't forget to commit your changes. + +Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still +generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for +example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still +have our whoopsie error-handler but i like to be more explicit in my control flow. + +In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new +Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and +InternalServerError. All three should extend phps Base Exception class. + +Here is my NotFound.php for example. + +```php + $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody()->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} +``` + +Check if our code still works, try to trigger some errors, run phpstan and the fix command +and don't forget to commit your changes. + +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/app/data/pages/08-inversion-of-control.md b/app/data/pages/08-inversion-of-control.md new file mode 100644 index 0000000..21f4f23 --- /dev/null +++ b/app/data/pages/08-inversion-of-control.md @@ -0,0 +1,54 @@ +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) + +### Inversion of Control + +In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we +want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then +we would need to edit every one of our request handlers to call a different constructor of the class. + +The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that +instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done +with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). + +If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is +implemented, it will make sense. + +Change your `Hello` action to the following: + +```php +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: + +```php +$handler = new $className($response); +``` + +Of course we need to also update all the other handlers. + +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/app/data/pages/09-dependency-injector.md b/app/data/pages/09-dependency-injector.md new file mode 100644 index 0000000..7f7c6a2 --- /dev/null +++ b/app/data/pages/09-dependency-injector.md @@ -0,0 +1,213 @@ +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) + +### Dependency Injector + +A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when +the class is instantiated. + +Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work +with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look +for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). + +I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) +out of the box. + +After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: + +```php +addDefinitions([ + \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), +]); + +return $builder->build(); +``` + +In this file we create a containerbuilder, add some definitions to it and return the container. +As the container supports autowiring we only need to define services where we want to use a specific implementation of +an interface. + +In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to +tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second +exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. + +Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), +as we will use that extensively. + +Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally +to create our Handler-Object + +```php +$container = require __DIR__ . '/../config/dependencies.php'; +assert($container instanceof \Psr\Container\ContainerInterface); + +$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); +assert($request instanceof \Psr\Http\Message\ServerRequestInterface); +``` + +The other part that has to be changed is the dispatching of the route. Before you had the following code: + +```php +$className = $routeInfo[1]; +$handler = new $className($response); +assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Change that to the following: + +```php +/** @var RequestHandlerInterface $handler */ +$className = $routeInfo[1]; +$handler = $container->get($className); +assert($handler instanceof RequestHandlerInterface); +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Make sure to use the container fetch the response object in the catch blocks as well: + +```php +} catch (MethodNotAllowed) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(404); + $response->getBody()->write('Not Found'); +} +``` + +Now all your controller constructor dependencies will be automatically resolved with PHP-DI. + +We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons +in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call +`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we +want to work with a different date in a test. + +Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation +that creates us a DateTimeImmutable object with the current date and time. + +src/Service/Time/Now.php: +```php +namespace Lubian\NoFramework\Service\Time; + +interface Now +{ + public function __invoke(): \DateTimeImmutable; +} +``` +src/Service/Time/SystemClockNow.php: +```php +namespace Lubian\NoFramework\Service\Time; + +final class SystemClockNow implements Now +{ + + public function __invoke(): \DateTimeImmutable + { + return new \DateTimeImmutable; + } +} +``` +If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and +update the handle-method to use the new class property: + +```php +getAttribute('name', 'Stranger'); + $nowAsString = ($this->now)()->format('H:i:s'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowAsString); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI +automatically figures out what classes are requested in the constructor and tries to create the objects needed. + +But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred +S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: + +```php + public function __construct( + private ResponseInterface $response, + private Now $now, + ) +``` + +When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation +should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: + +```php +\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), +``` + +we could also use the PHP-DI create method to delegate the object creation to the container implementation: +```php +\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), +``` + +this way the container can try to resolve any dependencies that the class might have internally, but prefer the other +method because we are not depending on this specific dependency injection implementation. + +Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are +requesting the Hello action. + +If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As +we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way +we wont get any warnings for this particular issue, but for any other issues we add to our code. + +Update the phpstan.neon file to include a "baseline" file: + +``` +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` + +if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and +ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just +'baseline' + +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/app/data/pages/10-invoker.md b/app/data/pages/10-invoker.md new file mode 100644 index 0000000..3033fae --- /dev/null +++ b/app/data/pages/10-invoker.md @@ -0,0 +1,102 @@ +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) + +### Invoker + +Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the +one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long +with some services and not the whole Requestobject with all its various properties. + +If we take our Hello action for example we only need a response object, the time service and the 'name' information from +the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named +the class hello and it would be redundant to also call the the method hello. So an updated version of that class could +look like this: + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + string $name = 'Stranger', + ): ResponseInterface + { + $body = $this->response->getBody(); + $nowString = $now->get()->format('H:i:s'); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowString); + return $response + ->withBody($body) + ->withStatus(200); + } +} +``` + +It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short +closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the +rootpath of our application yet. + +```php +$r->addRoute('GET', '/hello[/{name}]', Hello::class); +$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); +$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +``` + +In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the +route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that +class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what +arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router +if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple +callable we would need to manually use reflection as well to resolve all the arguments. + +But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) +which handles all of that for us. + +After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo +switch section in the Bootstrap.php file to use the container->call() method: + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2]; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$response = $container->call($handler, $args); +``` + +Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. + +But by now you should know that I do not like to depend on specific implementations and the call method is not defined in +the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container +or any other implementation. + +Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific +container implementation so we could use it with any container that implements the ContainerInterface. And best of all +the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that +we could implement if we ever want to write our own implementation or we could write an adapter that uses a different +class that solves the same problem. + +But for now we are using the solution provided by PHP-DI. +So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2] ?? []; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$invoker = $container->get(InvokerInterface::class); +assert($invoker instanceof InvokerInterface); +$response = $invoker->call($handler, $args); +assert($response instanceof ResponseInterface); +``` + +Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) +by php, and even some more. + +But let us move on to something more fun and add some templating functionality to our application as we are trying to build +a website in the end. + +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/app/data/pages/11-templating.md b/app/data/pages/11-templating.md new file mode 100644 index 0000000..3759664 --- /dev/null +++ b/app/data/pages/11-templating.md @@ -0,0 +1,240 @@ +[<< previous](10-invoker.md) | [next >>](12-configuration.md) + +### Templating + +A template engine is not necessary with PHP because the language itself can take care of that. But it can make things +like escaping values easier. They also make it easier to draw a clear line between your application logic and the +template files which should only put your variables into the HTML code. + +A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please +also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. +Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. + +For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install +that package before you continue (`composer require mustache/mustache`). + +Another well known alternative would be [Twig](http://twig.sensiolabs.org/). + +Now please go and have a look at the source code of the +[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class +does not implement an interface. + +You could just type hint against the concrete class. But the problem with this approach is that you create tight +coupling. + +In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the +implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want +to add functionality to the engine. You can't do that without going back and changing all your code that is tightly +coupled. + +What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need +another implementation, you just implement that interface in your new class and inject the new class instead. + +Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). +This sounds a lot more complicated than it is, so just follow along. + +First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). +This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. +A class can implement multiple interfaces if necessary. + +So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a +new folder in your `src/` folder with the name `Template` where you can put all the template related things. + +In there create a new interface `Renderer.php` that looks like this: + +```php + $data + * @return string + */ + public function render(string $template, array $data = []) : string; +} +``` + +Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file +`MustacheRenderer.php` with the following content: + +```php +engine->render($template, $data); + } +} +``` + +As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple +and only fulfills the interface. + +Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know +which implementation he has to inject when you hint for the interface. Add this line: + +```php +[ + ... + \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) + ->constructor(new Mustache_Engine), +] +``` + +Now update the Hello.php class to require an implementation of our renderer interface +and use that to render a string using mustache syntax. + + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + Renderer $renderer, + string $name = 'Stranger', + ): ResponseInterface { + $body = $response->getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render( + 'Hello {{name}}, the time is {{now}}!', + $data, + ); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} +``` + +Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. +But what we want is template files, so let's go back and change that. + +To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the +`dependencies.php` file and add the following code: + +```php +[ + ... + Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), + Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), +] +``` + +We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. +Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have +to rename all the template files. + +To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the +dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader +in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object +we can then use it in the factory. + +In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: + +``` +

Hello World

+Hello {{ name }} +``` + +Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` + +Navigate to the hello page in your browser to make sure everything works. + +One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies +file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration +values. + +Lets create a 'Settings' class in our './src' Folder: + +```php +addDefinitions([ + Settings::class => fn () => require __DIR__ '/settings.php', + ResponseInterface::class => create(Response::class), + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + Renderer::class => fn (ME $me) => new Mustache($me), + MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), +]); + +return $builder->build(); +``` + + + +And as always, don't forget to commit your changes. + + +[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/app/data/pages/12-configuration.md b/app/data/pages/12-configuration.md new file mode 100644 index 0000000..a44dfd5 --- /dev/null +++ b/app/data/pages/12-configuration.md @@ -0,0 +1,201 @@ +[<< previous](11-templating.md) | [next >>](13-refactoring.md) + +### Configuration + +In the last chapter we added some more definitions to our dependencies.php in that definitions +we needed to pass quite a few configuration settings and filesystem strings to the constructors +of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. + +As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. + +As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. + +Lets create a 'Settings' class in our './src' Folder: + +```php +filePath; + } +} +``` + +If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files +and craft a settings object from them. + +As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another +factory for our Container because everyone should have a Friend :) + +```php +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn () => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} +``` + +For this to work we need to change our dependencies.php file to just return the array of definitions: +And here we can instantly use the Settings object to create our template engine. + +```php + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), +]; +``` + +Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: +require __DIR__ . '/../vendor/autoload.php'; + +```php +... +error_reporting(E_ALL); + +$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); +$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); +... +``` + +Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. + +[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/app/data/pages/13-refactoring.md b/app/data/pages/13-refactoring.md new file mode 100644 index 0000000..067e168 --- /dev/null +++ b/app/data/pages/13-refactoring.md @@ -0,0 +1,377 @@ +[<< previous](12-configuration.md) | [next >>](14-middleware.md) + +### Refactoring + +By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no +reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. +After all the bootstrap file should just set up the classes needed for the handling logic and execute them. + +At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. +As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the +method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non +static and extracted an interface. + +'./src/Http/Emitter.php' +```php +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + echo $response->getBody(); + } +} +``` +After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following +code in the Bootstrap.php to emit the response: + +```php +/** @var Emitter $emitter */ +$emitter = $container->get(Emitter::class); +$emitter->emit($response); +``` + +If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily +write an adapter that implements your emitter interface and wraps that more advanced emitter + +Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and +calling the routerhandler that in the passes the request to a function and gets the response. + +For this to steps to be seperated we are going to create two more classes: +1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object +2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the + requestobject, fetches the correct object from the container and calls it to create a response. + +Lets create the HandlerInterface first: + +```php +getAttribute($this->routeAttributeName, false); + assert($handler !== false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} + +``` + +We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. +The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler +as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in +the next chapter. + +```php +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} +``` + +Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. +```php +[ + '...', + Emitter::class => fn (BasicEmitter $e) => $e, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, +], +``` + +And then we can update our Bootstrap.php to fetch all the services and let them handle the request. + +```php +... +$routeMiddleWare = $container->get(MiddlewareInterface::class); +assert($routeMiddleWare instanceof MiddlewareInterface); +$handler = $container->get(RoutedRequestHandler::class); +assert($handler instanceof RequestHandlerInterface); +$emitter = $container->get(Emitter::class); +assert($emitter instanceof Emitter); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$response = $routeMiddleWare->process($request, $handler); +$emitter->emit($response); +``` +Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of +code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). + +So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. + +I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class +should require to function properly. + +* A RequestFactory + We want our Kernel to be able to build the request itself +* An Emitter + Without an Emitter we will not be able to send the response to the client +* RouteMiddleware + To decore the request with the correct handler for the requested route +* RequestHandler + To delegate the request to the correct funtion that creates the response + +As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface +to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our +interface: + +```php +factory::fromGlobals(); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} +``` + +For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: + +```php +routeMiddleware->process($request, $this->handler); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} + +``` + +We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines + +```php +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); +``` + +You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking +at the Whoops output and adding the needed definitions to the dependencies.php file. + +And as always, don't forget to commit your changes. + +[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/app/data/pages/14-middleware.md b/app/data/pages/14-middleware.md new file mode 100644 index 0000000..e698327 --- /dev/null +++ b/app/data/pages/14-middleware.md @@ -0,0 +1,298 @@ +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) + +### Middleware + +In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain +a bit more about what this interface does and why it is used in many applications. + +The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets +passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all +the middlewars to the client/emitter. + +So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the +response after it gets created by our handlers. + +So lets take a look at the middleware and the requesthandler interfaces + +```php +interface MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; +} + +interface RequestHandlerInterface +{ + public function handle(ServerRequestInterface $request): ResponseInterface; +} +``` + +The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a +requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the +response. + +But the middleware could just ignore the handler and produce a response on its own as the interface just requires us +to produce a response. + +A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users +that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the +database. + +In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates +the request with an 'isAuthenticated' attribute. + +If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that +response is not already cached, than we let the handler create the response and store that in the cache for a few +seconds + +```php +interface CacheInterface +{ + public function get(string $key, callable $resolver, int $ttl): mixed; +} +``` + +The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one +defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses +the callable to produce the value and stores it for the time specified in ttl. + +so lets write our caching middleware: + +```php +final class CachingMiddleware implements MiddlewareInterface +{ + public function __construct(private CacheInterface $cache){} + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { + $key = $request->getUri()->getPath(); + return $this->cache->get($key, fn() => $handler->handle($request), 10); + } + return $handler->handle($request); + } +} +``` + +we can also modify the response after it has been created by our application, for example we could implement a gzip +middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser +know that our application is used to serve dank memes: + +```php +final class DankMemeMiddleware implements MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $response = $handler->handle($request); + return $response->withAddedHeader('Meme', 'Dank'); + } +} +``` + +but for our application we are going to just add two external middlewares: + +* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. +* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware + +```bash +composer require middlewares/trailing-slash +composer require middlewares/whoops +``` + +The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the +application as well as the middleware stack. + +Our desired request -> response flow looks something like this: + + Client + | ^ + v | + Kernel + | ^ + v | + Whoops Middleware + | ^ + v | + TrailingSlash + | ^ + v | + Routing + | ^ + v | + ContainerResolver + | ^ + v | + Controller/Action + +As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every +middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. + +```php +interface Pipeline +{ + public function dispatch(ServerRequestInterface $request): ResponseInterface; +} +``` + +And our implementation looks something like this: + +```php + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} +``` + +Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code +that should produce our response. + +In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument +and store that itself as the current tip. + +There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) + +Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline +Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline + +```php +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} +``` + +And configure the container to use the Factory to create the Pipeline: + +```php + ..., + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + ... +``` +And of course a new file called middlewares.php in our config folder: +```php +pipeline->dispatch($request); +} +``` + +Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our +pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a +dependency container, if you want you can do that as well. + +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) diff --git a/app/phpstan-baseline.neon b/app/phpstan-baseline.neon new file mode 100644 index 0000000..61697a1 --- /dev/null +++ b/app/phpstan-baseline.neon @@ -0,0 +1,7 @@ +parameters: + ignoreErrors: + - + message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" + count: 1 + path: src/Http/InvokerRoutedHandler.php + diff --git a/app/phpstan.neon b/app/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/app/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/app/public/css/spectre-exp.min.css b/app/public/css/spectre-exp.min.css new file mode 100644 index 0000000..d313774 --- /dev/null +++ b/app/public/css/spectre-exp.min.css @@ -0,0 +1 @@ +/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/app/public/css/spectre-icons.min.css b/app/public/css/spectre-icons.min.css new file mode 100644 index 0000000..0276f7b --- /dev/null +++ b/app/public/css/spectre-icons.min.css @@ -0,0 +1 @@ +/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/app/public/css/spectre.min.css b/app/public/css/spectre.min.css new file mode 100644 index 0000000..0fe23d9 --- /dev/null +++ b/app/public/css/spectre.min.css @@ -0,0 +1 @@ +/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/app/public/index.php b/app/public/index.php new file mode 100644 index 0000000..d93da3a --- /dev/null +++ b/app/public/index.php @@ -0,0 +1,5 @@ +getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render('hello', $data); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/app/src/Action/Other.php b/app/src/Action/Other.php new file mode 100644 index 0000000..895796e --- /dev/null +++ b/app/src/Action/Other.php @@ -0,0 +1,19 @@ +getBody(); + + $body->write('This works too!'); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/app/src/Action/Page.php b/app/src/Action/Page.php new file mode 100644 index 0000000..5d27e84 --- /dev/null +++ b/app/src/Action/Page.php @@ -0,0 +1,35 @@ +byTitle($page); + $content = $this->linkFilter($page->content); + $content = $parsedown->parse($content); + $html = $renderer->render('page', ['content' => $content]); + $response->getBody()->write($html); + return $response; + } + + private function linkFilter(string $content): string + { + $content = preg_replace('/\(\d\d-/m', '(', $content); + return str_replace('.md)', ')', $content); + } +} diff --git a/app/src/Bootstrap.php b/app/src/Bootstrap.php new file mode 100644 index 0000000..3abc2e5 --- /dev/null +++ b/app/src/Bootstrap.php @@ -0,0 +1,40 @@ +getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); diff --git a/app/src/Exception/InternalServerError.php b/app/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/app/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +factory::fromGlobals(); + } + + /** + * @param UriInterface|string $uri + * @param array $serverParams + */ + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} diff --git a/app/src/Factory/FileSystemSettingsProvider.php b/app/src/Factory/FileSystemSettingsProvider.php new file mode 100644 index 0000000..f071078 --- /dev/null +++ b/app/src/Factory/FileSystemSettingsProvider.php @@ -0,0 +1,22 @@ +filePath; + assert($settings instanceof Settings); + return $settings; + } +} diff --git a/app/src/Factory/PipelineProvider.php b/app/src/Factory/PipelineProvider.php new file mode 100644 index 0000000..77738f8 --- /dev/null +++ b/app/src/Factory/PipelineProvider.php @@ -0,0 +1,25 @@ +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} diff --git a/app/src/Factory/RequestFactory.php b/app/src/Factory/RequestFactory.php new file mode 100644 index 0000000..2b17abc --- /dev/null +++ b/app/src/Factory/RequestFactory.php @@ -0,0 +1,11 @@ +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn (): Settings => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} diff --git a/app/src/Factory/SettingsProvider.php b/app/src/Factory/SettingsProvider.php new file mode 100644 index 0000000..ce1c5f0 --- /dev/null +++ b/app/src/Factory/SettingsProvider.php @@ -0,0 +1,10 @@ +|class-string|callable $handler - */ - public function addRoute( - string $method, - string $path, - array|string|callable $handler, - ): self; -} \ No newline at end of file diff --git a/app/src/Http/BasicEmitter.php b/app/src/Http/BasicEmitter.php new file mode 100644 index 0000000..aefe088 --- /dev/null +++ b/app/src/Http/BasicEmitter.php @@ -0,0 +1,38 @@ +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + + echo $response->getBody(); + } +} diff --git a/app/src/Http/ContainerPipeline.php b/app/src/Http/ContainerPipeline.php new file mode 100644 index 0000000..816cedd --- /dev/null +++ b/app/src/Http/ContainerPipeline.php @@ -0,0 +1,82 @@ + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} diff --git a/app/src/Http/Emitter.php b/app/src/Http/Emitter.php new file mode 100644 index 0000000..ce4c035 --- /dev/null +++ b/app/src/Http/Emitter.php @@ -0,0 +1,10 @@ +getAttribute($this->routeAttributeName, false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} diff --git a/app/src/Http/Pipeline.php b/app/src/Http/Pipeline.php new file mode 100644 index 0000000..1a9dcda --- /dev/null +++ b/app/src/Http/Pipeline.php @@ -0,0 +1,11 @@ + $routes - */ - public function __construct( - private readonly ResponseFactoryInterface $responseFactory, - private readonly string $routeAttributeName = '__route_handler', - private array $routes = [], - private Dispatcher|null $dispatcher = null, - ) - { - } - - /** - * @throws MethodNotAllowed - * @throws NotFound - */ - private function decorateRequest(ServerRequestInterface $request): ServerRequestInterface - { - $this->dispatcher ??= $this->createDispatcher(); - $routeInfo = $this->dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath() - ); - - if ($routeInfo[0] === Dispatcher::NOT_FOUND) { - throw new NotFound; - } - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - - return $request->withAttribute($this->routeAttributeName, $routeInfo[1]); - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - - return $handler->handle($request); - } - - private function createDispatcher(): Dispatcher - { - return simpleDispatcher(function (RouteCollector $r) { - foreach ($this->routes as $route) { - $r->addRoute($route[0], $route[1], $route[2]); - } - }); - } - - /** - * @inheritDoc - */ - public function addRoute(string $method, string $path, array|string|callable $handler,): AddRoute - { - $this->routes[] = [$method, $path, $handler]; - return $this; - } -} diff --git a/app/src/Http/RouteMiddleware.php b/app/src/Http/RouteMiddleware.php new file mode 100644 index 0000000..e3df6f8 --- /dev/null +++ b/app/src/Http/RouteMiddleware.php @@ -0,0 +1,69 @@ +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} diff --git a/app/src/Http/RoutedRequestHandler.php b/app/src/Http/RoutedRequestHandler.php new file mode 100644 index 0000000..a7407c9 --- /dev/null +++ b/app/src/Http/RoutedRequestHandler.php @@ -0,0 +1,10 @@ +routeMiddleware->process($request, $this->handler); + return $this->pipeline->dispatch($request); } - public function run(ServerRequestInterface |null $request = null): void + public function run(): void { - $request ??= $this->createRequest(); + $request = $this->requestFactory->fromGlobals(); $response = $this->handle($request); $this->emitter->emit($response); } - - private function createRequest(): ServerRequestInterface - { - try { - $request = $this->container->get(ServerRequestInterface::class); - assert($request instanceof ServerRequestInterface); - return $request; - } catch (Throwable $t) { - throw new InternalServerError( - 'could not get Request from container, please configure the container ' . - 'in order to use run() wihtout a request', - $t->getCode(), - $t, - ); - } - } - - public function addRoute( - string $method, - string $path, - array|string|callable $handler): AddRoute - { - $this->routeMiddleware->addRoute($method, $path, $handler); - return $this; - } } diff --git a/app/src/Middleware/CacheMiddleware.php b/app/src/Middleware/CacheMiddleware.php new file mode 100644 index 0000000..482a057 --- /dev/null +++ b/app/src/Middleware/CacheMiddleware.php @@ -0,0 +1,33 @@ +getMethod() === 'GET') { + $key = (string) $request->getUri(); + $key = base64_encode($key); + $callback = fn () => $handler->handle($request); + $response = new Response(); + $body = $this->cache->get($key, function (ItemInterface $item) use ($callback) { + $item->expiresAfter(120); + return (string) $callback()->getBody(); + }); + $response->getBody()->write($body); + return $response; + } + return $handler->handle($request); + } +} diff --git a/app/src/Model/MarkdownPage.php b/app/src/Model/MarkdownPage.php new file mode 100644 index 0000000..503774f --- /dev/null +++ b/app/src/Model/MarkdownPage.php @@ -0,0 +1,13 @@ + $this->repo->all(); + return $this->cache->get('ALLPAGES', function (ItemInterface $item) use ($callback) { + $item->expiresAfter(30); + return $callback(); + }); + } + + public function byId(int $id): MarkdownPage + { + $callback = fn () => $this->repo->byId($id); + return $this->cache->get('PAGE' . $id, function (ItemInterface $item) use ($callback) { + $item->expiresAfter(30); + return $callback(); + }); + } + + public function byTitle(string $title): MarkdownPage + { + $callback = fn () => $this->repo->byTitle($title); + return $this->cache->get('PAGE' . $title, function (ItemInterface $item) use ($callback) { + $item->expiresAfter(30); + return $callback(); + }); + } +} diff --git a/app/src/Repository/MarkdownPageFilesystem.php b/app/src/Repository/MarkdownPageFilesystem.php new file mode 100644 index 0000000..abd4107 --- /dev/null +++ b/app/src/Repository/MarkdownPageFilesystem.php @@ -0,0 +1,63 @@ +dataPath . '*.md'); + assert(is_array($fileNames)); + return array_map(function (string $name): MarkdownPage { + usleep(100000); + $content = file_get_contents($name); + $name = str_replace($this->dataPath, '', $name); + $name = str_replace('.md', '', $name); + $id = (int) substr($name, 0, 2); + $title = substr($name, 3); + return new MarkdownPage($id, $title, $content); + }, $fileNames); + } + + public function byId(int $id): MarkdownPage + { + $callback = fn (MarkdownPage $p): bool => $p->id === $id; + $filtered = array_values(array_filter($this->all(), $callback)); + if (count($filtered) === 0) { + throw new NotFound; + } + return $filtered[0]; + } + + public function byTitle(string $title): MarkdownPage + { + $callback = fn (MarkdownPage $p): bool => $p->title === $title; + $filtered = array_values(array_filter($this->all(), $callback)); + if (count($filtered) === 0) { + throw new NotFound; + } + return $filtered[0]; + } +} diff --git a/app/src/Repository/MarkdownPageRepo.php b/app/src/Repository/MarkdownPageRepo.php new file mode 100644 index 0000000..b823af0 --- /dev/null +++ b/app/src/Repository/MarkdownPageRepo.php @@ -0,0 +1,17 @@ +engine->render($template, $data); + } +} diff --git a/app/src/Template/Renderer.php b/app/src/Template/Renderer.php new file mode 100644 index 0000000..ff916ed --- /dev/null +++ b/app/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data = []): string; +} diff --git a/app/templates/hello.html b/app/templates/hello.html new file mode 100644 index 0000000..15a4cd2 --- /dev/null +++ b/app/templates/hello.html @@ -0,0 +1,6 @@ +{{> partials/head }} +
+

Hello {{name}}

+

The time is {{now}}

+
+{{> partials/foot }} diff --git a/app/templates/page.html b/app/templates/page.html new file mode 100644 index 0000000..c3c5284 --- /dev/null +++ b/app/templates/page.html @@ -0,0 +1,5 @@ +{{> partials/head }} +
+ {{{content}}} +
+{{> partials/foot }} diff --git a/app/templates/partials/foot.html b/app/templates/partials/foot.html new file mode 100644 index 0000000..17c7245 --- /dev/null +++ b/app/templates/partials/foot.html @@ -0,0 +1,3 @@ +
+ + \ No newline at end of file diff --git a/app/templates/partials/head.html b/app/templates/partials/head.html new file mode 100644 index 0000000..9d57085 --- /dev/null +++ b/app/templates/partials/head.html @@ -0,0 +1,11 @@ + + + + + Title + + + + + +
diff --git a/implementation/14-middleware/.php-cs-fixer.php b/implementation/14-middleware/.php-cs-fixer.php new file mode 100644 index 0000000..705a7d7 --- /dev/null +++ b/implementation/14-middleware/.php-cs-fixer.php @@ -0,0 +1,38 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config' + ]) + ); \ No newline at end of file diff --git a/implementation/14-middleware/.phpcs.xml.dist b/implementation/14-middleware/.phpcs.xml.dist new file mode 100644 index 0000000..3b433f6 --- /dev/null +++ b/implementation/14-middleware/.phpcs.xml.dist @@ -0,0 +1,9 @@ + + + + + src + config + + + \ No newline at end of file diff --git a/implementation/14-middleware/composer.json b/implementation/14-middleware/composer.json new file mode 100644 index 0000000..a1372b3 --- /dev/null +++ b/implementation/14-middleware/composer.json @@ -0,0 +1,50 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3", + "mustache/mustache": "^2.14", + "psr/http-server-middleware": "^1.0", + "middlewares/trailing-slash": "^2.0", + "middlewares/whoops": "^2.0" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require-dev": { + "phpstan/phpstan": "^1.5", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0", + "squizlabs/php_codesniffer": "^3.6", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.1", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "mnapoli/hard-mode": "^0.3.0", + "psalm/phar": "^4.22" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + } +} diff --git a/implementation/14-middleware/composer.lock b/implementation/14-middleware/composer.lock new file mode 100644 index 0000000..3ac6853 --- /dev/null +++ b/implementation/14-middleware/composer.lock @@ -0,0 +1,1815 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "580bbe25eceb19e89a36dc4e5541d44c", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-22T03:54:36+00:00" + }, + { + "name": "middlewares/trailing-slash", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/middlewares/trailing-slash.git", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", + "shasum": "" + }, + "require": { + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to normalize the trailing slash of the uri path", + "homepage": "https://github.com/middlewares/trailing-slash", + "keywords": [ + "http", + "middleware", + "normalize", + "path", + "psr-15", + "psr-7", + "slash" + ], + "support": { + "issues": "https://github.com/middlewares/trailing-slash/issues", + "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" + }, + "time": "2020-12-02T00:06:55+00:00" + }, + { + "name": "middlewares/utils", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/middlewares/utils.git", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v2.16", + "guzzlehttp/psr7": "^2.0", + "laminas/laminas-diactoros": "^2.4", + "nyholm/psr7": "^1.0", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "slim/psr7": "^1.4", + "squizlabs/php_codesniffer": "^3.5", + "sunrise/http-message": "^1.0", + "sunrise/http-server-request": "^1.0", + "sunrise/stream": "^1.0.15", + "sunrise/uri": "^1.0.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\Utils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Common utils for PSR-15 middleware packages", + "homepage": "https://github.com/middlewares/utils", + "keywords": [ + "PSR-11", + "http", + "middleware", + "psr-15", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/middlewares/utils/issues", + "source": "https://github.com/middlewares/utils/tree/v3.3.0" + }, + "time": "2021-07-04T17:56:23+00:00" + }, + { + "name": "middlewares/whoops", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/middlewares/whoops.git", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.5", + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^5.0 || ^7.0", + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to use Whoops as error handler", + "homepage": "https://github.com/middlewares/whoops", + "keywords": [ + "error", + "http", + "middleware", + "psr-15", + "psr-7", + "server", + "whoops" + ], + "support": { + "issues": "https://github.com/middlewares/whoops/issues", + "source": "https://github.com/middlewares/whoops/tree/v2.0.2" + }, + "time": "2022-01-27T20:31:30+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/master" + }, + "time": "2018-10-30T17:12:04+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "doctrine/coding-standard", + "version": "8.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/coding-standard.git", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Müller", + "email": "st.mueller@dzh-online.de" + } + ], + "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "doctrine", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/doctrine/coding-standard/issues", + "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" + }, + "time": "2021-04-03T10:54:55+00:00" + }, + { + "name": "mnapoli/hard-mode", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/hard-mode.git", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", + "shasum": "" + }, + "require": { + "doctrine/coding-standard": "^8.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Hard mode for PHP", + "support": { + "issues": "https://github.com/mnapoli/hard-mode/issues", + "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" + }, + "time": "2020-10-12T07:54:37+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.5.0" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-24T18:18:00+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^1.2.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" + }, + "time": "2021-11-18T09:30:29+00:00" + }, + { + "name": "psalm/phar", + "version": "4.22.0", + "source": { + "type": "git", + "url": "https://github.com/psalm/phar.git", + "reference": "feebed09c9782d9aaa819b794d880c2671ba0e4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/psalm/phar/zipball/feebed09c9782d9aaa819b794d880c2671ba0e4c", + "reference": "feebed09c9782d9aaa819b794d880c2671ba0e4c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "vimeo/psalm": "*" + }, + "bin": [ + "psalm.phar" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer-based Psalm Phar", + "support": { + "issues": "https://github.com/psalm/phar/issues", + "source": "https://github.com/psalm/phar/tree/4.22.0" + }, + "time": "2022-02-27T11:01:37+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/14-middleware/config/dependencies.php b/implementation/14-middleware/config/dependencies.php new file mode 100644 index 0000000..e84ad53 --- /dev/null +++ b/implementation/14-middleware/config/dependencies.php @@ -0,0 +1,42 @@ + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, + Emitter::class => fn (BasicEmitter $e) => $e, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), +]; diff --git a/implementation/14-middleware/config/middlewares.php b/implementation/14-middleware/config/middlewares.php new file mode 100644 index 0000000..71dd461 --- /dev/null +++ b/implementation/14-middleware/config/middlewares.php @@ -0,0 +1,11 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); + $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/implementation/14-middleware/config/settings.php b/implementation/14-middleware/config/settings.php new file mode 100644 index 0000000..b489400 --- /dev/null +++ b/implementation/14-middleware/config/settings.php @@ -0,0 +1,11 @@ +aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/14-middleware/public/index.php b/implementation/14-middleware/public/index.php new file mode 100644 index 0000000..d93da3a --- /dev/null +++ b/implementation/14-middleware/public/index.php @@ -0,0 +1,5 @@ +getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render('hello', $data); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/14-middleware/src/Action/Other.php b/implementation/14-middleware/src/Action/Other.php new file mode 100644 index 0000000..895796e --- /dev/null +++ b/implementation/14-middleware/src/Action/Other.php @@ -0,0 +1,19 @@ +getBody(); + + $body->write('This works too!'); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/14-middleware/src/Bootstrap.php b/implementation/14-middleware/src/Bootstrap.php new file mode 100644 index 0000000..3abc2e5 --- /dev/null +++ b/implementation/14-middleware/src/Bootstrap.php @@ -0,0 +1,40 @@ +getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); diff --git a/implementation/14-middleware/src/Exception/InternalServerError.php b/implementation/14-middleware/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/14-middleware/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +factory::fromGlobals(); + } + + /** + * @param UriInterface|string $uri + * @param array $serverParams + */ + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} diff --git a/implementation/14-middleware/src/Factory/FileSystemSettingsProvider.php b/implementation/14-middleware/src/Factory/FileSystemSettingsProvider.php new file mode 100644 index 0000000..f071078 --- /dev/null +++ b/implementation/14-middleware/src/Factory/FileSystemSettingsProvider.php @@ -0,0 +1,22 @@ +filePath; + assert($settings instanceof Settings); + return $settings; + } +} diff --git a/implementation/14-middleware/src/Factory/PipelineProvider.php b/implementation/14-middleware/src/Factory/PipelineProvider.php new file mode 100644 index 0000000..77738f8 --- /dev/null +++ b/implementation/14-middleware/src/Factory/PipelineProvider.php @@ -0,0 +1,25 @@ +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} diff --git a/implementation/14-middleware/src/Factory/RequestFactory.php b/implementation/14-middleware/src/Factory/RequestFactory.php new file mode 100644 index 0000000..2b17abc --- /dev/null +++ b/implementation/14-middleware/src/Factory/RequestFactory.php @@ -0,0 +1,11 @@ +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn (): Settings => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} diff --git a/implementation/14-middleware/src/Factory/SettingsProvider.php b/implementation/14-middleware/src/Factory/SettingsProvider.php new file mode 100644 index 0000000..ce1c5f0 --- /dev/null +++ b/implementation/14-middleware/src/Factory/SettingsProvider.php @@ -0,0 +1,10 @@ +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + + echo $response->getBody(); + } +} diff --git a/implementation/14-middleware/src/Http/ContainerPipeline.php b/implementation/14-middleware/src/Http/ContainerPipeline.php new file mode 100644 index 0000000..816cedd --- /dev/null +++ b/implementation/14-middleware/src/Http/ContainerPipeline.php @@ -0,0 +1,82 @@ + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} diff --git a/implementation/14-middleware/src/Http/Emitter.php b/implementation/14-middleware/src/Http/Emitter.php new file mode 100644 index 0000000..ce4c035 --- /dev/null +++ b/implementation/14-middleware/src/Http/Emitter.php @@ -0,0 +1,10 @@ +getAttribute($this->routeAttributeName, false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} diff --git a/implementation/14-middleware/src/Http/Pipeline.php b/implementation/14-middleware/src/Http/Pipeline.php new file mode 100644 index 0000000..1a9dcda --- /dev/null +++ b/implementation/14-middleware/src/Http/Pipeline.php @@ -0,0 +1,11 @@ +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} diff --git a/implementation/14-middleware/src/Http/RoutedRequestHandler.php b/implementation/14-middleware/src/Http/RoutedRequestHandler.php new file mode 100644 index 0000000..a7407c9 --- /dev/null +++ b/implementation/14-middleware/src/Http/RoutedRequestHandler.php @@ -0,0 +1,10 @@ +pipeline->dispatch($request); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} diff --git a/implementation/14-middleware/src/Service/Time/Now.php b/implementation/14-middleware/src/Service/Time/Now.php new file mode 100644 index 0000000..79224b3 --- /dev/null +++ b/implementation/14-middleware/src/Service/Time/Now.php @@ -0,0 +1,10 @@ +engine->render($template, $data); + } +} diff --git a/implementation/14-middleware/src/Template/Renderer.php b/implementation/14-middleware/src/Template/Renderer.php new file mode 100644 index 0000000..ff916ed --- /dev/null +++ b/implementation/14-middleware/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data = []): string; +} diff --git a/implementation/14-middleware/templates/hello.html b/implementation/14-middleware/templates/hello.html new file mode 100644 index 0000000..0e21f2a --- /dev/null +++ b/implementation/14-middleware/templates/hello.html @@ -0,0 +1,11 @@ + + + + + Hello World + + +

Hello {{name}}

+

The time is {{now}}

+ + \ No newline at end of file From 46b98d16a6e9fc853f4ff3203d09d6fa1ab52665 Mon Sep 17 00:00:00 2001 From: lubiana Date: Tue, 5 Apr 2022 00:02:52 +0200 Subject: [PATCH 277/314] update readme --- .gitignore | 5 +- README.md | 36 +- Vagrantfile | 1 - app/cli-config.php | 13 + app/composer.json | 11 +- app/composer.lock | 1742 ++++++++++++- app/config/dependencies.php | 25 +- app/config/middlewares.php | 3 +- app/config/routes.php | 3 +- app/config/settings.php | 15 +- app/src/Action/Page.php | 36 +- app/src/Factory/DoctrineEm.php | 32 + app/src/Factory/SettingsContainerProvider.php | 3 +- app/src/Middleware/CacheMiddleware.php | 22 +- app/src/Model/MarkdownPage.php | 16 +- app/src/Repository/CachedMarkdownPageRepo.php | 20 +- .../Repository/DoctrineMarkdownPageRepo.php | 60 + app/src/Repository/MarkdownPageFilesystem.php | 7 +- app/src/Repository/MarkdownPageRepo.php | 2 + app/src/Settings.php | 17 + app/templates/pagelist.html | 11 + app/templates/partials/head.html | 2 +- implementation/16-caching/.php-cs-fixer.php | 38 + implementation/16-caching/.phpcs.xml.dist | 9 + implementation/16-caching/composer.json | 54 + implementation/16-caching/composer.lock | 2273 +++++++++++++++++ .../16-caching/config/dependencies.php | 54 + .../16-caching/config/middlewares.php | 13 + implementation/16-caching/config/routes.php | 14 + implementation/16-caching/config/settings.php | 12 + .../data/pages/01-front-controller.md | 53 + .../16-caching/data/pages/02-composer.md | 75 + .../16-caching/data/pages/03-error-handler.md | 79 + .../data/pages/04-development-helpers.md | 260 ++ .../16-caching/data/pages/05-http.md | 124 + .../16-caching/data/pages/06-router.md | 101 + .../data/pages/07-dispatching-to-a-class.md | 137 + .../data/pages/08-inversion-of-control.md | 54 + .../data/pages/09-dependency-injector.md | 213 ++ .../16-caching/data/pages/10-invoker.md | 102 + .../16-caching/data/pages/11-templating.md | 240 ++ .../16-caching/data/pages/12-configuration.md | 201 ++ .../16-caching/data/pages/13-refactoring.md | 377 +++ .../16-caching/data/pages/14-middleware.md | 298 +++ .../16-caching/phpstan-baseline.neon | 7 + implementation/16-caching/phpstan.neon | 8 + .../16-caching/public/css/spectre-exp.min.css | 1 + .../public/css/spectre-icons.min.css | 1 + .../16-caching/public/css/spectre.min.css | 1 + implementation/16-caching/public/favicon.ico | Bin 0 -> 15086 bytes implementation/16-caching/public/index.php | 5 + implementation/16-caching/src/.gitkeep | 0 .../16-caching/src/Action/Hello.php | 31 + .../16-caching/src/Action/Other.php | 19 + implementation/16-caching/src/Action/Page.php | 35 + implementation/16-caching/src/Bootstrap.php | 40 + .../src/Exception/InternalServerError.php | 9 + .../src/Exception/MethodNotAllowed.php | 9 + .../16-caching/src/Exception/NotFound.php | 9 + .../src/Factory/ContainerProvider.php | 10 + .../src/Factory/DiactorosRequestFactory.php | 28 + .../Factory/FileSystemSettingsProvider.php | 22 + .../src/Factory/PipelineProvider.php | 25 + .../16-caching/src/Factory/RequestFactory.php | 11 + .../src/Factory/SettingsContainerProvider.php | 25 + .../src/Factory/SettingsProvider.php | 10 + .../16-caching/src/Http/BasicEmitter.php | 38 + .../16-caching/src/Http/ContainerPipeline.php | 82 + .../16-caching/src/Http/Emitter.php | 10 + .../src/Http/InvokerRoutedHandler.php | 34 + .../16-caching/src/Http/Pipeline.php | 11 + .../16-caching/src/Http/RouteMiddleware.php | 69 + .../src/Http/RoutedRequestHandler.php | 10 + implementation/16-caching/src/Kernel.php | 32 + .../src/Middleware/CacheMiddleware.php | 36 + .../16-caching/src/Model/MarkdownPage.php | 13 + .../src/Repository/CachedMarkdownPageRepo.php | 46 + .../src/Repository/MarkdownPageFilesystem.php | 63 + .../src/Repository/MarkdownPageRepo.php | 17 + .../16-caching/src/Service/Time/Now.php | 10 + .../src/Service/Time/SystemClockNow.php | 13 + implementation/16-caching/src/Settings.php | 16 + .../src/Template/MustacheRenderer.php | 17 + .../16-caching/src/Template/Renderer.php | 11 + .../16-caching/templates/hello.html | 6 + implementation/16-caching/templates/page.html | 5 + .../16-caching/templates/partials/foot.html | 3 + .../16-caching/templates/partials/head.html | 11 + 88 files changed, 7546 insertions(+), 176 deletions(-) create mode 100644 app/cli-config.php create mode 100644 app/src/Factory/DoctrineEm.php create mode 100644 app/src/Repository/DoctrineMarkdownPageRepo.php create mode 100644 app/templates/pagelist.html create mode 100644 implementation/16-caching/.php-cs-fixer.php create mode 100644 implementation/16-caching/.phpcs.xml.dist create mode 100644 implementation/16-caching/composer.json create mode 100644 implementation/16-caching/composer.lock create mode 100644 implementation/16-caching/config/dependencies.php create mode 100644 implementation/16-caching/config/middlewares.php create mode 100644 implementation/16-caching/config/routes.php create mode 100644 implementation/16-caching/config/settings.php create mode 100644 implementation/16-caching/data/pages/01-front-controller.md create mode 100644 implementation/16-caching/data/pages/02-composer.md create mode 100644 implementation/16-caching/data/pages/03-error-handler.md create mode 100644 implementation/16-caching/data/pages/04-development-helpers.md create mode 100644 implementation/16-caching/data/pages/05-http.md create mode 100644 implementation/16-caching/data/pages/06-router.md create mode 100644 implementation/16-caching/data/pages/07-dispatching-to-a-class.md create mode 100644 implementation/16-caching/data/pages/08-inversion-of-control.md create mode 100644 implementation/16-caching/data/pages/09-dependency-injector.md create mode 100644 implementation/16-caching/data/pages/10-invoker.md create mode 100644 implementation/16-caching/data/pages/11-templating.md create mode 100644 implementation/16-caching/data/pages/12-configuration.md create mode 100644 implementation/16-caching/data/pages/13-refactoring.md create mode 100644 implementation/16-caching/data/pages/14-middleware.md create mode 100644 implementation/16-caching/phpstan-baseline.neon create mode 100644 implementation/16-caching/phpstan.neon create mode 100644 implementation/16-caching/public/css/spectre-exp.min.css create mode 100644 implementation/16-caching/public/css/spectre-icons.min.css create mode 100644 implementation/16-caching/public/css/spectre.min.css create mode 100644 implementation/16-caching/public/favicon.ico create mode 100644 implementation/16-caching/public/index.php create mode 100644 implementation/16-caching/src/.gitkeep create mode 100644 implementation/16-caching/src/Action/Hello.php create mode 100644 implementation/16-caching/src/Action/Other.php create mode 100644 implementation/16-caching/src/Action/Page.php create mode 100644 implementation/16-caching/src/Bootstrap.php create mode 100644 implementation/16-caching/src/Exception/InternalServerError.php create mode 100644 implementation/16-caching/src/Exception/MethodNotAllowed.php create mode 100644 implementation/16-caching/src/Exception/NotFound.php create mode 100644 implementation/16-caching/src/Factory/ContainerProvider.php create mode 100644 implementation/16-caching/src/Factory/DiactorosRequestFactory.php create mode 100644 implementation/16-caching/src/Factory/FileSystemSettingsProvider.php create mode 100644 implementation/16-caching/src/Factory/PipelineProvider.php create mode 100644 implementation/16-caching/src/Factory/RequestFactory.php create mode 100644 implementation/16-caching/src/Factory/SettingsContainerProvider.php create mode 100644 implementation/16-caching/src/Factory/SettingsProvider.php create mode 100644 implementation/16-caching/src/Http/BasicEmitter.php create mode 100644 implementation/16-caching/src/Http/ContainerPipeline.php create mode 100644 implementation/16-caching/src/Http/Emitter.php create mode 100644 implementation/16-caching/src/Http/InvokerRoutedHandler.php create mode 100644 implementation/16-caching/src/Http/Pipeline.php create mode 100644 implementation/16-caching/src/Http/RouteMiddleware.php create mode 100644 implementation/16-caching/src/Http/RoutedRequestHandler.php create mode 100644 implementation/16-caching/src/Kernel.php create mode 100644 implementation/16-caching/src/Middleware/CacheMiddleware.php create mode 100644 implementation/16-caching/src/Model/MarkdownPage.php create mode 100644 implementation/16-caching/src/Repository/CachedMarkdownPageRepo.php create mode 100644 implementation/16-caching/src/Repository/MarkdownPageFilesystem.php create mode 100644 implementation/16-caching/src/Repository/MarkdownPageRepo.php create mode 100644 implementation/16-caching/src/Service/Time/Now.php create mode 100644 implementation/16-caching/src/Service/Time/SystemClockNow.php create mode 100644 implementation/16-caching/src/Settings.php create mode 100644 implementation/16-caching/src/Template/MustacheRenderer.php create mode 100644 implementation/16-caching/src/Template/Renderer.php create mode 100644 implementation/16-caching/templates/hello.html create mode 100644 implementation/16-caching/templates/page.html create mode 100644 implementation/16-caching/templates/partials/foot.html create mode 100644 implementation/16-caching/templates/partials/head.html diff --git a/.gitignore b/.gitignore index cc205bd..c84df77 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ **/vendor/ **/.php-cs-fixer.cache .idea/ -.vagrant/ \ No newline at end of file +.vagrant/ +/identifier.sqlite +*.log +**/*.sqlite diff --git a/README.md b/README.md index af7b810..a636596 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,37 @@ -# No Framework +# Create a PHP application without a Framework + +Hello and welcome to this tutorial with helps you in understanding how to write complex apps without the help of +a framework. This tutorial is not for people who have never written PHP before, you should at least have some +experience with object oriented PHP and be able to look at the official PHP-Documentation to figure out what +a function or class we are using does. + +I often hear people talking about frameworks as a solution to all the problems that you have in software development. +But in my opinion its even worse to use a framework if you do not know what you are doing, because often are fighting +more against the framework than actually solving the problem you should be working on. Even if you know what you are +doing i think it is good to get to know how the frameworks you are using work under the hood and what challenges they +actually solve for you. + +## Credit: + +This tutorial is based on the great [tutorial by Patrick Louys](https://github.com/PatrickLouys/no-framework-tutorial). +My version is way more opiniated and uses some newer PHP features. But you should still check out his tutorial which is +still very great and helped me personally a lot in taking the next step in my knowledge about PHP development. There is +also an [amazon book](https://patricklouys.com/professional-php/) which expands on the topics covered in this tutorial. + +## Getting started. + +As I am using a fairly new version of PHP in this tutorial I have added a Vagrantfile to this tutorial. If you do not +have PHP8.1 installed on your computer you can use the following commands to try out all the examples: + +```shell +vagrant up +vagrant ssh +cd app +``` + +I have exposed the port 1234 to be used in the VM, if you would like to use another one you are free to modify the +Vagrantfile. + + [Start](01-front-controller.md) diff --git a/Vagrantfile b/Vagrantfile index 0b30662..56a4db4 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -17,6 +17,5 @@ Vagrant.configure("2") do |config| echo -e 'zend_extension=xdebug\nxdebug.client_host=10.0.2.2\n' >> /etc/php/conf.d/tutorial.ini echo -e 'xdebug.client_port=9003\nxdebug.mode=debug\n' >> /etc/php/conf.d/tutorial.ini echo -e 'zend.assertions=1\n' >> /etc/php/conf.d/tutorial.ini - SHELL end diff --git a/app/cli-config.php b/app/cli-config.php new file mode 100644 index 0000000..fbc6598 --- /dev/null +++ b/app/cli-config.php @@ -0,0 +1,13 @@ +getContainer(); + +return ConsoleRunner::createHelperSet($container->get(EntityManagerInterface::class)); diff --git a/app/composer.json b/app/composer.json index 9cbc356..04edde0 100644 --- a/app/composer.json +++ b/app/composer.json @@ -12,7 +12,8 @@ "middlewares/trailing-slash": "^2.0", "middlewares/whoops": "^2.0", "erusev/parsedown": "^1.7", - "symfony/cache": "^6.0" + "symfony/cache": "^6.0", + "doctrine/orm": "^2.11" }, "autoload": { "psr-4": { @@ -33,8 +34,7 @@ "phpstan/extension-installer": "^1.1", "phpstan/phpstan-strict-rules": "^1.1", "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0", - "psalm/phar": "^4.22" + "mnapoli/hard-mode": "^0.3.0" }, "config": { "allow-plugins": { @@ -43,7 +43,10 @@ } }, "scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1234 -t public" + ], "phpstan": "./vendor/bin/phpstan analyze", "baseline": "./vendor/bin/phpstan analyze --generate-baseline", "check": "./vendor/bin/phpcs", diff --git a/app/composer.lock b/app/composer.lock index b10158c..904cfb4 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -4,8 +4,937 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0f81a412e25ca1269493dface2804540", + "content-hash": "a3762dd11bab0c9e948d3a73b7f252b9", "packages": [ + { + "name": "doctrine/cache", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/331b4d5dbaeab3827976273e9356b3b453c300ce", + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce", + "shasum": "" + }, + "require": { + "php": "~7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "alcaeus/mongo-php-adapter": "^1.1", + "cache/integration-tests": "dev-master", + "doctrine/coding-standard": "^8.0", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "predis/predis": "~1.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.2 || ^6.0@dev", + "symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", + "keywords": [ + "abstraction", + "apcu", + "cache", + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" + ], + "support": { + "issues": "https://github.com/doctrine/cache/issues", + "source": "https://github.com/doctrine/cache/tree/2.1.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "type": "tidelift" + } + ], + "time": "2021-07-17T14:49:29+00:00" + }, + { + "name": "doctrine/collections", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/1958a744696c6bb3bb0d28db2611dc11610e78af", + "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af", + "shasum": "" + }, + "require": { + "php": "^7.1.3 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5", + "vimeo/psalm": "^4.2.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", + "keywords": [ + "array", + "collections", + "iterators", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/collections/issues", + "source": "https://github.com/doctrine/collections/tree/1.6.8" + }, + "time": "2021-08-10T18:51:53+00:00" + }, + { + "name": "doctrine/common", + "version": "3.2.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "295082d3750987065912816a9d536c2df735f637" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/295082d3750987065912816a9d536c2df735f637", + "reference": "295082d3750987065912816a9d536c2df735f637", + "shasum": "" + }, + "require": { + "doctrine/persistence": "^2.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^1.4.1", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.0", + "symfony/phpunit-bridge": "^4.0.5", + "vimeo/psalm": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", + "homepage": "https://www.doctrine-project.org/projects/common.html", + "keywords": [ + "common", + "doctrine", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/common/issues", + "source": "https://github.com/doctrine/common/tree/3.2.2" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", + "type": "tidelift" + } + ], + "time": "2022-02-02T09:15:57+00:00" + }, + { + "name": "doctrine/dbal", + "version": "3.3.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "83f779beaea1893c0bece093ab2104c6d15a7f26" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/83f779beaea1893c0bece093ab2104c6d15a7f26", + "reference": "83f779beaea1893c0bece093ab2104c6d15a7f26", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/cache": "^1.11|^2.0", + "doctrine/deprecations": "^0.5.3", + "doctrine/event-manager": "^1.0", + "php": "^7.3 || ^8.0", + "psr/cache": "^1|^2|^3", + "psr/log": "^1|^2|^3" + }, + "require-dev": { + "doctrine/coding-standard": "9.0.0", + "jetbrains/phpstorm-stubs": "2021.1", + "phpstan/phpstan": "1.4.6", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "9.5.16", + "psalm/plugin-phpunit": "0.16.1", + "squizlabs/php_codesniffer": "3.6.2", + "symfony/cache": "^5.2|^6.0", + "symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0", + "vimeo/psalm": "4.22.0" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\DBAL\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", + "homepage": "https://www.doctrine-project.org/projects/dbal.html", + "keywords": [ + "abstraction", + "database", + "db2", + "dbal", + "mariadb", + "mssql", + "mysql", + "oci8", + "oracle", + "pdo", + "pgsql", + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlite", + "sqlserver", + "sqlsrv" + ], + "support": { + "issues": "https://github.com/doctrine/dbal/issues", + "source": "https://github.com/doctrine/dbal/tree/3.3.4" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", + "type": "tidelift" + } + ], + "time": "2022-03-20T18:37:29+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "v0.5.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "9504165960a1f83cc1480e2be1dd0a0478561314" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314", + "reference": "9504165960a1f83cc1480e2be1dd0a0478561314", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0|^7.0|^8.0", + "phpunit/phpunit": "^7.0|^8.0|^9.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v0.5.3" + }, + "time": "2021-03-21T12:59:47+00:00" + }, + { + "name": "doctrine/event-manager", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": "<2.9@dev" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/1.1.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2020-05-29T18:28:51+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "vimeo/psalm": "^4.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2021-10-22T20:16:43+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-03-03T08:28:38+00:00" + }, + { + "name": "doctrine/lexer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2022-02-28T11:07:21+00:00" + }, + { + "name": "doctrine/orm", + "version": "2.11.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/orm.git", + "reference": "9c351e044478135aec1755e2c0c0493a4b6309db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/orm/zipball/9c351e044478135aec1755e2c0c0493a4b6309db", + "reference": "9c351e044478135aec1755e2c0c0493a4b6309db", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/cache": "^1.12.1 || ^2.1.1", + "doctrine/collections": "^1.5", + "doctrine/common": "^3.0.3", + "doctrine/dbal": "^2.13.1 || ^3.2", + "doctrine/deprecations": "^0.5.3", + "doctrine/event-manager": "^1.1", + "doctrine/inflector": "^1.4 || ^2.0", + "doctrine/instantiator": "^1.3", + "doctrine/lexer": "^1.0", + "doctrine/persistence": "^2.2", + "ext-ctype": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3", + "symfony/console": "^3.0 || ^4.0 || ^5.0 || ^6.0", + "symfony/polyfill-php72": "^1.23", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "doctrine/annotations": "<1.13 || >= 2.0" + }, + "require-dev": { + "doctrine/annotations": "^1.13", + "doctrine/coding-standard": "^9.0", + "phpbench/phpbench": "^0.16.10 || ^1.0", + "phpstan/phpstan": "1.4.6", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "squizlabs/php_codesniffer": "3.6.2", + "symfony/cache": "^4.4 || ^5.4 || ^6.0", + "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0", + "vimeo/psalm": "4.22.0" + }, + "suggest": { + "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0", + "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" + }, + "bin": [ + "bin/doctrine" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\ORM\\": "lib/Doctrine/ORM" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "Object-Relational-Mapper for PHP", + "homepage": "https://www.doctrine-project.org/projects/orm.html", + "keywords": [ + "database", + "orm" + ], + "support": { + "issues": "https://github.com/doctrine/orm/issues", + "source": "https://github.com/doctrine/orm/tree/2.11.2" + }, + "time": "2022-03-09T15:23:58+00:00" + }, + { + "name": "doctrine/persistence", + "version": "2.4.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/persistence.git", + "reference": "092a52b71410ac1795287bb5135704ef07d18dd0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/092a52b71410ac1795287bb5135704ef07d18dd0", + "reference": "092a52b71410ac1795287bb5135704ef07d18dd0", + "shasum": "" + }, + "require": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/collections": "^1.0", + "doctrine/deprecations": "^0.5.3", + "doctrine/event-manager": "^1.0", + "php": "^7.1 || ^8.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0" + }, + "conflict": { + "doctrine/annotations": "<1.0 || >=2.0", + "doctrine/common": "<2.10" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.11", + "doctrine/annotations": "^1.0", + "doctrine/coding-standard": "^9.0", + "doctrine/common": "^3.0", + "phpstan/phpstan": "1.4.6", + "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6.0", + "vimeo/psalm": "4.21.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "src/Common", + "Doctrine\\Persistence\\": "src/Persistence" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", + "homepage": "https://doctrine-project.org/projects/persistence.html", + "keywords": [ + "mapper", + "object", + "odm", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/persistence/issues", + "source": "https://github.com/doctrine/persistence/tree/2.4.1" + }, + "time": "2022-03-22T06:44:40+00:00" + }, { "name": "erusev/parsedown", "version": "1.7.4", @@ -129,16 +1058,16 @@ }, { "name": "laminas/laminas-diactoros", - "version": "2.8.0", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", "shasum": "" }, "require": { @@ -224,7 +1153,7 @@ "type": "community_bridge" } ], - "time": "2021-09-22T03:54:36+00:00" + "time": "2022-03-29T20:12:16+00:00" }, { "name": "middlewares/trailing-slash", @@ -1278,6 +2207,101 @@ ], "time": "2021-08-17T15:35:52+00:00" }, + { + "name": "symfony/console", + "version": "v6.0.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "3bebf4108b9e07492a2a4057d207aa5a77d146b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/3bebf4108b9e07492a2a4057d207aa5a77d146b1", + "reference": "3bebf4108b9e07492a2a4057d207aa5a77d146b1", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.4|^6.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/lock": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.0.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-02-25T10:48:52+00:00" + }, { "name": "symfony/deprecation-contracts", "version": "v2.5.0", @@ -1345,6 +2369,495 @@ ], "time": "2021-07-12T14:48:14+00:00" }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "30885182c981ab175d4d034db0f6f469898070ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", + "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-10-20T20:35:02+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "81b86b50cf841a64252b439e738e97f4a34e2783" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", + "reference": "81b86b50cf841a64252b439e738e97f4a34e2783", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-23T21:10:46+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T09:17:38+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-04T08:16:47+00:00" + }, { "name": "symfony/service-contracts", "version": "v2.5.0", @@ -1428,6 +2941,91 @@ ], "time": "2021-11-04T16:48:04+00:00" }, + { + "name": "symfony/string", + "version": "v6.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/522144f0c4c004c80d56fa47e40e17028e2eefc2", + "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.0" + }, + "require-dev": { + "symfony/error-handler": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/translation-contracts": "^2.0|^3.0", + "symfony/var-exporter": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v6.0.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:55:41+00:00" + }, { "name": "symfony/var-exporter", "version": "v6.0.6", @@ -1808,16 +3406,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.5.0", + "version": "1.5.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" + "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/39953ac1452a8843702ee41a35b4861d3e8207a7", + "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7", "shasum": "" }, "require": { @@ -1843,7 +3441,7 @@ "description": "PHPStan - PHP Static Analysis Tool", "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.0" + "source": "https://github.com/phpstan/phpstan/tree/1.5.3" }, "funding": [ { @@ -1863,7 +3461,7 @@ "type": "tidelift" } ], - "time": "2022-03-24T18:18:00+00:00" + "time": "2022-03-30T21:55:08+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -1916,41 +3514,6 @@ }, "time": "2021-11-18T09:30:29+00:00" }, - { - "name": "psalm/phar", - "version": "4.22.0", - "source": { - "type": "git", - "url": "https://github.com/psalm/phar.git", - "reference": "feebed09c9782d9aaa819b794d880c2671ba0e4c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/psalm/phar/zipball/feebed09c9782d9aaa819b794d880c2671ba0e4c", - "reference": "feebed09c9782d9aaa819b794d880c2671ba0e4c", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "vimeo/psalm": "*" - }, - "bin": [ - "psalm.phar" - ], - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer-based Psalm Phar", - "support": { - "issues": "https://github.com/psalm/phar/issues", - "source": "https://github.com/psalm/phar/tree/4.22.0" - }, - "time": "2022-02-27T11:01:37+00:00" - }, { "name": "slevomat/coding-standard", "version": "6.4.1", @@ -2068,89 +3631,6 @@ }, "time": "2021-12-12T21:44:58+00:00" }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, { "name": "symfony/var-dumper", "version": "v6.0.6", @@ -2304,5 +3784,5 @@ "php": "^8.1" }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.3.0" } diff --git a/app/config/dependencies.php b/app/config/dependencies.php index e1ea3bf..4a97f31 100644 --- a/app/config/dependencies.php +++ b/app/config/dependencies.php @@ -1,8 +1,10 @@ fn (SystemClockNow $n) => $n, + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, + Emitter::class => fn (BasicEmitter $e) => $e, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, + CacheInterface::class => fn (FilesystemAdapter $a) => $a, + MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, + + // Factories ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, - Emitter::class => fn (BasicEmitter $e) => $e, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), - RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - CacheInterface::class => fn (FilesystemAdapter $a) => $a, MarkdownPageFilesystem::class => fn (Settings $s) => new MarkdownPageFilesystem($s->pagesPath), - CachedMarkdownPageRepo::class => fn (CacheInterface $c, MarkdownPageFilesystem $r) => new CachedMarkdownPageRepo($c, $r), - MarkdownPageRepo::class => fn (MarkdownPageFilesystem $r) => $r, + CachedMarkdownPageRepo::class => fn (CacheInterface $c, MarkdownPageFilesystem $r, Settings $s) => new CachedMarkdownPageRepo($c, $r, $s), + EntityManagerInterface::class => fn (DoctrineEm $f) => $f->create(), ]; diff --git a/app/config/middlewares.php b/app/config/middlewares.php index 891cc83..459547e 100644 --- a/app/config/middlewares.php +++ b/app/config/middlewares.php @@ -1,12 +1,13 @@ addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/page/{page}', Page::class); + $r->addRoute('GET', '/page', [Page::class, 'list']); + $r->addRoute('GET', '/page/{page}', [Page::class, 'show']); $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); }; diff --git a/app/config/settings.php b/app/config/settings.php index 8a0861d..5c58216 100644 --- a/app/config/settings.php +++ b/app/config/settings.php @@ -3,10 +3,21 @@ use Lubian\NoFramework\Settings; return new Settings( - environment: 'dev', + environment: 'prod', dependenciesFile: __DIR__ . '/dependencies.php', middlewaresFile: __DIR__ . '/middlewares.php', templateDir: __DIR__ . '/../templates', templateExtension: '.html', - pagesPath: __DIR__ . '/../data/pages/' + pagesPath: __DIR__ . '/../data/pages/', + connection: [ + 'driver' => 'pdo_sqlite', + 'user' => '', + 'password' => '', + 'path' => __DIR__ . '/../data/db.sqlite', + ], + doctrine: [ + 'devMode' => true, + 'metadataDirs' => [__DIR__ . '/../src/Model/'], + 'cacheDir' => __DIR__ . '/../data/cache/', + ], ); diff --git a/app/src/Action/Page.php b/app/src/Action/Page.php index 5d27e84..9fb86c2 100644 --- a/app/src/Action/Page.php +++ b/app/src/Action/Page.php @@ -2,6 +2,8 @@ namespace Lubian\NoFramework\Action; +use Lubian\NoFramework\Model\MarkdownPage; +use Lubian\NoFramework\Repository\MarkdownPageFilesystem; use Lubian\NoFramework\Repository\MarkdownPageRepo; use Lubian\NoFramework\Template\Renderer; use Parsedown; @@ -12,19 +14,33 @@ use function str_replace; class Page { - public function __invoke( + public function __construct( + private ResponseInterface $response, + private MarkdownPageRepo $repo, + private Parsedown $parsedown, + private Renderer $renderer, + ){} + public function show( string $page, - ResponseInterface $response, - MarkdownPageRepo $repo, - Parsedown $parsedown, - Renderer $renderer, ): ResponseInterface { - $page = $repo->byTitle($page); + $page = $this->repo->byTitle($page); $content = $this->linkFilter($page->content); - $content = $parsedown->parse($content); - $html = $renderer->render('page', ['content' => $content]); - $response->getBody()->write($html); - return $response; + $content = $this->parsedown->parse($content); + $html = $this->renderer->render('page', ['content' => $content, 'title' => $page->title]); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map( + fn (MarkdownPage $p) => ['title' => $p->title, 'id' => $p->id], + $this->repo->all() + ); + $html = $this->renderer->render('pagelist', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } private function linkFilter(string $content): string diff --git a/app/src/Factory/DoctrineEm.php b/app/src/Factory/DoctrineEm.php new file mode 100644 index 0000000..be24e7f --- /dev/null +++ b/app/src/Factory/DoctrineEm.php @@ -0,0 +1,32 @@ +settings->doctrine['devMode']); + + $config->setMetadataDriverImpl( + new AttributeDriver( + $this->settings->doctrine['metadataDirs'] + ) + ); + + return EntityManager::create( + $this->settings->connection, + $config, + ); + } +} \ No newline at end of file diff --git a/app/src/Factory/SettingsContainerProvider.php b/app/src/Factory/SettingsContainerProvider.php index ad6b0b0..baf278b 100644 --- a/app/src/Factory/SettingsContainerProvider.php +++ b/app/src/Factory/SettingsContainerProvider.php @@ -18,8 +18,9 @@ final class SettingsContainerProvider implements ContainerProvider $builder = new ContainerBuilder; $settings = $this->settingsProvider->getSettings(); $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn (): Settings => $settings; + $dependencies[Settings::class] = $settings; $builder->addDefinitions($dependencies); + // $builder->enableCompilation('/tmp'); return $builder->build(); } } diff --git a/app/src/Middleware/CacheMiddleware.php b/app/src/Middleware/CacheMiddleware.php index 482a057..2ff6f6e 100644 --- a/app/src/Middleware/CacheMiddleware.php +++ b/app/src/Middleware/CacheMiddleware.php @@ -3,6 +3,7 @@ namespace Lubian\NoFramework\Middleware; use Laminas\Diactoros\Response; +use Lubian\NoFramework\Settings; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; @@ -10,23 +11,30 @@ use Psr\Http\Server\RequestHandlerInterface; use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Cache\ItemInterface; +use function base64_encode; + final class CacheMiddleware implements MiddlewareInterface { - public function __construct(private CacheInterface $cache){} + public function __construct( + private CacheInterface $cache, + private Response\Serializer $serializer, + private Settings $settings, + ) + { + } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { - if ($request->getMethod() === 'GET') { + if ($request->getMethod() === 'GET' && !$this->settings->isDev()) { $key = (string) $request->getUri(); $key = base64_encode($key); $callback = fn () => $handler->handle($request); - $response = new Response(); - $body = $this->cache->get($key, function (ItemInterface $item) use ($callback) { + $cached = $this->cache->get($key, function (ItemInterface $item) use ($callback) { $item->expiresAfter(120); - return (string) $callback()->getBody(); + $response = $callback(); + return $this->serializer::toString($response); }); - $response->getBody()->write($body); - return $response; + return $this->serializer::fromString($cached); } return $handler->handle($request); } diff --git a/app/src/Model/MarkdownPage.php b/app/src/Model/MarkdownPage.php index 503774f..73f001d 100644 --- a/app/src/Model/MarkdownPage.php +++ b/app/src/Model/MarkdownPage.php @@ -2,12 +2,22 @@ namespace Lubian\NoFramework\Model; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\GeneratedValue; +use Doctrine\ORM\Mapping\Id; + +#[Entity] class MarkdownPage { public function __construct( - public readonly int $id, - public readonly string $title, - public readonly string $content, + #[Id, Column, GeneratedValue] + public int|null $id = null, + #[Column] + public string $title, + #[Column(type: Types::TEXT)] + public string $content, ) { } } diff --git a/app/src/Repository/CachedMarkdownPageRepo.php b/app/src/Repository/CachedMarkdownPageRepo.php index 9efdb17..1b4a1fb 100644 --- a/app/src/Repository/CachedMarkdownPageRepo.php +++ b/app/src/Repository/CachedMarkdownPageRepo.php @@ -3,14 +3,16 @@ namespace Lubian\NoFramework\Repository; use Lubian\NoFramework\Model\MarkdownPage; +use Lubian\NoFramework\Settings; use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Cache\ItemInterface; class CachedMarkdownPageRepo implements MarkdownPageRepo { public function __construct( - private CacheInterface $cache, - private MarkdownPageRepo $repo, + private readonly CacheInterface $cache, + private readonly MarkdownPageRepo $repo, + private readonly Settings $settings, ) { } @@ -20,6 +22,9 @@ class CachedMarkdownPageRepo implements MarkdownPageRepo public function all(): array { $callback = fn () => $this->repo->all(); + if ($this->settings->isDev()) { + return $callback(); + } return $this->cache->get('ALLPAGES', function (ItemInterface $item) use ($callback) { $item->expiresAfter(30); return $callback(); @@ -29,6 +34,9 @@ class CachedMarkdownPageRepo implements MarkdownPageRepo public function byId(int $id): MarkdownPage { $callback = fn () => $this->repo->byId($id); + if ($this->settings->isDev()) { + return $callback(); + } return $this->cache->get('PAGE' . $id, function (ItemInterface $item) use ($callback) { $item->expiresAfter(30); return $callback(); @@ -38,9 +46,17 @@ class CachedMarkdownPageRepo implements MarkdownPageRepo public function byTitle(string $title): MarkdownPage { $callback = fn () => $this->repo->byTitle($title); + if ($this->settings->isDev()) { + return $callback(); + } return $this->cache->get('PAGE' . $title, function (ItemInterface $item) use ($callback) { $item->expiresAfter(30); return $callback(); }); } + + public function save(MarkdownPage $page): MarkdownPage + { + return $this->repo->save($page); + } } diff --git a/app/src/Repository/DoctrineMarkdownPageRepo.php b/app/src/Repository/DoctrineMarkdownPageRepo.php new file mode 100644 index 0000000..f107c9e --- /dev/null +++ b/app/src/Repository/DoctrineMarkdownPageRepo.php @@ -0,0 +1,60 @@ + + */ + private EntityRepository $repo; + public function __construct( + private EntityManagerInterface $entityManager + ){ + $this->repo = $this->entityManager->getRepository(MarkdownPage::class); + } + + /** + * @inheritDoc + */ + public function all(): array + { + usleep(rand(500, 1500) * 1000); + return $this->repo->findAll(); + } + + public function byId(int $id): MarkdownPage + { + usleep(rand(500, 1500) * 1000); + $page = $this->repo->findOneBy(['id' => $id]); + if (!$page instanceof MarkdownPage){ + throw new NotFound; + } + return $page; + } + + public function byTitle(string $title): MarkdownPage + { + usleep(rand(500, 1500) * 1000); + $page = $this->repo->findOneBy(['title' => $title]); + if (!$page instanceof MarkdownPage){ + throw new NotFound; + } + return $page; + } + + public function save(MarkdownPage $page): MarkdownPage + { + $this->entityManager->persist($page); + $this->entityManager->flush(); + return $page; + } +} \ No newline at end of file diff --git a/app/src/Repository/MarkdownPageFilesystem.php b/app/src/Repository/MarkdownPageFilesystem.php index abd4107..1d70998 100644 --- a/app/src/Repository/MarkdownPageFilesystem.php +++ b/app/src/Repository/MarkdownPageFilesystem.php @@ -31,7 +31,7 @@ final class MarkdownPageFilesystem implements MarkdownPageRepo $fileNames = glob($this->dataPath . '*.md'); assert(is_array($fileNames)); return array_map(function (string $name): MarkdownPage { - usleep(100000); + usleep(rand(200, 500) * 1000); $content = file_get_contents($name); $name = str_replace($this->dataPath, '', $name); $name = str_replace('.md', '', $name); @@ -60,4 +60,9 @@ final class MarkdownPageFilesystem implements MarkdownPageRepo } return $filtered[0]; } + + public function save(MarkdownPage $page): MarkdownPage + { + return $page; + } } diff --git a/app/src/Repository/MarkdownPageRepo.php b/app/src/Repository/MarkdownPageRepo.php index b823af0..3f80899 100644 --- a/app/src/Repository/MarkdownPageRepo.php +++ b/app/src/Repository/MarkdownPageRepo.php @@ -14,4 +14,6 @@ interface MarkdownPageRepo public function byId(int $id): MarkdownPage; public function byTitle(string $title): MarkdownPage; + + public function save(MarkdownPage $page): MarkdownPage; } diff --git a/app/src/Settings.php b/app/src/Settings.php index 885aa7b..a6e4218 100644 --- a/app/src/Settings.php +++ b/app/src/Settings.php @@ -4,6 +4,10 @@ namespace Lubian\NoFramework; final class Settings { + /** + * @param array{driver: string, user: string, password: string, path: string} $connection + * @param array{devMode: bool, metadataDirs: string[], cacheDir: string} $doctrine + */ public function __construct( public readonly string $environment, public readonly string $dependenciesFile, @@ -11,6 +15,19 @@ final class Settings public readonly string $templateDir, public readonly string $templateExtension, public readonly string $pagesPath, + /** + * @var array{driver: string, user: string, password: string, path: string} + */ + public readonly array $connection, + /** + * @var array{devMode: bool, metadataDirs: string[], cacheDir: string} + */ + public readonly array $doctrine, ) { } + + public function isDev(): bool + { + return $this->environment === 'dev'; + } } diff --git a/app/templates/pagelist.html b/app/templates/pagelist.html new file mode 100644 index 0000000..538e2c4 --- /dev/null +++ b/app/templates/pagelist.html @@ -0,0 +1,11 @@ +{{> partials/head }} +
+ +
+{{> partials/foot }} diff --git a/app/templates/partials/head.html b/app/templates/partials/head.html index 9d57085..421d387 100644 --- a/app/templates/partials/head.html +++ b/app/templates/partials/head.html @@ -2,7 +2,7 @@ - Title + No Framework: {{title}} diff --git a/implementation/16-caching/.php-cs-fixer.php b/implementation/16-caching/.php-cs-fixer.php new file mode 100644 index 0000000..705a7d7 --- /dev/null +++ b/implementation/16-caching/.php-cs-fixer.php @@ -0,0 +1,38 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config' + ]) + ); \ No newline at end of file diff --git a/implementation/16-caching/.phpcs.xml.dist b/implementation/16-caching/.phpcs.xml.dist new file mode 100644 index 0000000..3b433f6 --- /dev/null +++ b/implementation/16-caching/.phpcs.xml.dist @@ -0,0 +1,9 @@ + + + + + src + config + + + \ No newline at end of file diff --git a/implementation/16-caching/composer.json b/implementation/16-caching/composer.json new file mode 100644 index 0000000..e85cc50 --- /dev/null +++ b/implementation/16-caching/composer.json @@ -0,0 +1,54 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3", + "mustache/mustache": "^2.14", + "psr/http-server-middleware": "^1.0", + "middlewares/trailing-slash": "^2.0", + "middlewares/whoops": "^2.0", + "erusev/parsedown": "^1.7", + "symfony/cache": "^6.0" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require-dev": { + "phpstan/phpstan": "^1.5", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0", + "squizlabs/php_codesniffer": "^3.6", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.1", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "mnapoli/hard-mode": "^0.3.0" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1234 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + } +} diff --git a/implementation/16-caching/composer.lock b/implementation/16-caching/composer.lock new file mode 100644 index 0000000..0c626d9 --- /dev/null +++ b/implementation/16-caching/composer.lock @@ -0,0 +1,2273 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "5286ff6a5dbbe21ace2a7359b49b8780", + "packages": [ + { + "name": "erusev/parsedown", + "version": "1.7.4", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "support": { + "issues": "https://github.com/erusev/parsedown/issues", + "source": "https://github.com/erusev/parsedown/tree/1.7.x" + }, + "time": "2019-12-30T22:54:17+00:00" + }, + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-03-29T20:12:16+00:00" + }, + { + "name": "middlewares/trailing-slash", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/middlewares/trailing-slash.git", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", + "shasum": "" + }, + "require": { + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to normalize the trailing slash of the uri path", + "homepage": "https://github.com/middlewares/trailing-slash", + "keywords": [ + "http", + "middleware", + "normalize", + "path", + "psr-15", + "psr-7", + "slash" + ], + "support": { + "issues": "https://github.com/middlewares/trailing-slash/issues", + "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" + }, + "time": "2020-12-02T00:06:55+00:00" + }, + { + "name": "middlewares/utils", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/middlewares/utils.git", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v2.16", + "guzzlehttp/psr7": "^2.0", + "laminas/laminas-diactoros": "^2.4", + "nyholm/psr7": "^1.0", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "slim/psr7": "^1.4", + "squizlabs/php_codesniffer": "^3.5", + "sunrise/http-message": "^1.0", + "sunrise/http-server-request": "^1.0", + "sunrise/stream": "^1.0.15", + "sunrise/uri": "^1.0.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\Utils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Common utils for PSR-15 middleware packages", + "homepage": "https://github.com/middlewares/utils", + "keywords": [ + "PSR-11", + "http", + "middleware", + "psr-15", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/middlewares/utils/issues", + "source": "https://github.com/middlewares/utils/tree/v3.3.0" + }, + "time": "2021-07-04T17:56:23+00:00" + }, + { + "name": "middlewares/whoops", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/middlewares/whoops.git", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.5", + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^5.0 || ^7.0", + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to use Whoops as error handler", + "homepage": "https://github.com/middlewares/whoops", + "keywords": [ + "error", + "http", + "middleware", + "psr-15", + "psr-7", + "server", + "whoops" + ], + "support": { + "issues": "https://github.com/middlewares/whoops/issues", + "source": "https://github.com/middlewares/whoops/tree/v2.0.2" + }, + "time": "2022-01-27T20:31:30+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/master" + }, + "time": "2018-10-30T17:12:04+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "symfony/cache", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", + "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^1.1.7|^2|^3", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/var-exporter": "^5.4|^6.0" + }, + "conflict": { + "doctrine/dbal": "<2.13.1", + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/var-dumper": "<5.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^2.13.1|^3.0", + "predis/predis": "^1.1", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/2f7463f156cf9c665d9317e21a809c3bbff5754e", + "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "psr/cache": "^3.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.0.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-08-17T15:35:52+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-07-12T14:48:14+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-04T16:48:04+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "130229a482abf17635a685590958894dfb4b4360" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/130229a482abf17635a685590958894dfb4b4360", + "reference": "130229a482abf17635a685590958894dfb4b4360", + "shasum": "" + }, + "require": { + "php": ">=8.0.2" + }, + "require-dev": { + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "doctrine/coding-standard", + "version": "8.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/coding-standard.git", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Müller", + "email": "st.mueller@dzh-online.de" + } + ], + "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "doctrine", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/doctrine/coding-standard/issues", + "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" + }, + "time": "2021-04-03T10:54:55+00:00" + }, + { + "name": "mnapoli/hard-mode", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/hard-mode.git", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", + "shasum": "" + }, + "require": { + "doctrine/coding-standard": "^8.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Hard mode for PHP", + "support": { + "issues": "https://github.com/mnapoli/hard-mode/issues", + "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" + }, + "time": "2020-10-12T07:54:37+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.5.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/39953ac1452a8843702ee41a35b4861d3e8207a7", + "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.5.3" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-30T21:55:08+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^1.2.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" + }, + "time": "2021-11-18T09:30:29+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/16-caching/config/dependencies.php b/implementation/16-caching/config/dependencies.php new file mode 100644 index 0000000..376aea0 --- /dev/null +++ b/implementation/16-caching/config/dependencies.php @@ -0,0 +1,54 @@ + fn (SystemClockNow $n) => $n, + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, + Emitter::class => fn (BasicEmitter $e) => $e, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, + CacheInterface::class => fn (FilesystemAdapter $a) => $a, + MarkdownPageRepo::class => fn (MarkdownPageFilesystem $r) => $r, + + // Factories + ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), + Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + MarkdownPageFilesystem::class => fn (Settings $s) => new MarkdownPageFilesystem($s->pagesPath), + CachedMarkdownPageRepo::class => fn (CacheInterface $c, MarkdownPageFilesystem $r) => new CachedMarkdownPageRepo($c, $r), +]; diff --git a/implementation/16-caching/config/middlewares.php b/implementation/16-caching/config/middlewares.php new file mode 100644 index 0000000..459547e --- /dev/null +++ b/implementation/16-caching/config/middlewares.php @@ -0,0 +1,13 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/page/{page}', Page::class); + $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); + $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/implementation/16-caching/config/settings.php b/implementation/16-caching/config/settings.php new file mode 100644 index 0000000..8a0861d --- /dev/null +++ b/implementation/16-caching/config/settings.php @@ -0,0 +1,12 @@ +>](02-composer.md) + +### Front Controller + +A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. + +To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. + +A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. + +The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. + +But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. + +So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. + +Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: + +```php +>](02-composer.md) diff --git a/implementation/16-caching/data/pages/02-composer.md b/implementation/16-caching/data/pages/02-composer.md new file mode 100644 index 0000000..a25a4a8 --- /dev/null +++ b/implementation/16-caching/data/pages/02-composer.md @@ -0,0 +1,75 @@ +[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) + +### Composer + +[Composer](https://getcomposer.org/) is a dependency manager for PHP. + +Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do +something. With Composer, you can install third-party libraries for your application. + +If you don't have Composer installed already, head over to the website and install it. You can find Composer packages +for your project on [Packagist](https://packagist.org/). + +Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will +be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. + +Add the following content to the file: + +```json +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubiana", + "email": "lubiana@hannover.ccc.de" + } + ] +} +``` + +In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use +whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. +Just replace it with your namespace in your own code. + +I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. + +As the Bootstrap.php file is placed in that directory we should +add the namespace to the File as well. Here is my current Bootstrap.php +as a reference: + +```php +>](03-error-handler.md) diff --git a/implementation/16-caching/data/pages/03-error-handler.md b/implementation/16-caching/data/pages/03-error-handler.md new file mode 100644 index 0000000..60465d0 --- /dev/null +++ b/implementation/16-caching/data/pages/03-error-handler.md @@ -0,0 +1,79 @@ +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) + +### Error Handler + +An error handler allows you to customize what happens if your code results in an error. + +A nice error page with a lot of information for debugging goes a long way during development. So the first package +for your application will take care of that. + +I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. +If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, +you have total control over your project. + +An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) + +To install a new package, open up your `composer.json` and add the package to the require part. It should now look +like this: + +```php +"require": { + "php": ">=8.1.0", + "filp/whoops": "^2.14" +}, +``` + +Now run `composer update` in your console and it will be installed. + +Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, +i that case composer automatically installs the package and updates your composer.json-file. + +But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, +ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you +only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. + +**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message +can help someone to gain access to your system. Always show a user friendly error page instead and send an email to +yourself, write to a log or something similar. So only you can see the errors in the production environment. + +For development that does not make sense though -- you want a nice error page. The solution is to have an environment +switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in +case no environment has been set. + +Then after the error handler registration, throw an `Exception` to test if everything is working correctly. +Your `Bootstrap.php` should now look similar to this: + +```php +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (\Throwable $e) { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +throw new \Exception("Ooooopsie"); + +``` + +You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until +you get it working. Now would also be a good time for another commit. + + +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/implementation/16-caching/data/pages/04-development-helpers.md b/implementation/16-caching/data/pages/04-development-helpers.md new file mode 100644 index 0000000..74f913c --- /dev/null +++ b/implementation/16-caching/data/pages/04-development-helpers.md @@ -0,0 +1,260 @@ +[<< previous](03-error-handler.md) | [next >>](05-http.md) + +### Development Helpers + +I have added some more helpers to my composer.json that help me with development. As these are scripts and programms +used only for development they should not be used in a production environment. Composer has a specific sections in its +file called "dev-dependencies", everything that is required in this section does not get installen in production. + +Let's install our dev-helpers and i will explain them one by one: +`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` + +#### Static Code Analysis with phpstan + +Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or +create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements +that are not (or not always) available, if-statements that always are true and a lot of other stuff. + +A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. + +```php +/** + * @param \DateTime $date + * @return void + */ +function printDate($date) { + $date->format('Y-m-d H:i:s'); +} + +printDate('now'); +``` +if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` + +It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has +no use, and secondly, that we are calling the function with a string instead of a datetime object. + +```shell +1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + ------ --------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ --------------------------------------------------------------------------------------------- +30 Call to method DateTime::format() on a separate line has no effect. +33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. + ------ --------------------------------------------------------------------------------------------- +``` + +The second error is something that "declare strict-types" already catches for us, but the first error is something that +we usually would not discover easily without speccially looking for this errortype. + +We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and +path everytime we want to check our code for errors: + +```yaml +parameters: + level: max + paths: + - src +``` +now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project + +With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us +some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. + +```shell +composer require --dev phpstan/extension-installer +composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules +``` + +During the first install you need to allow the extension installer to actually install the extension. The second command +installs some more strict rulesets and activates them in phpstan. + +If we now rerun phpstan it already tells us about some errors we have made: + +``` + ------ ----------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ ----------------------------------------------------------------------------------------------- +10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider + using long ternary. +25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: + http://bit.ly/subtypeexception +26 Unreachable statement - code above always terminates. + ------ ----------------------------------------------------------------------------------------------- +``` + +The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove +that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific +problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working +on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages +everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we +are adding to our code. + +In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the +phpstan.neon file and run phpstan with the +'--generate-baseline' option: + +```yaml +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` +```shell +[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline +Note: Using configuration file /home/vagrant/app/phpstan.neon. + 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + + + [OK] Baseline generated with 1 error. + + +``` + +you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) + +#### PHP-CS-Fixer + +Another great tool is the php-cs-fixer, which just applies a specific style to your code. + +when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current +directory. + +You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) + +personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup +a configuration file that i use in all my projects .php-cs-fixer.php: + +```php +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + ]) + ); +``` + +#### PHP Codesniffer + +The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra +rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. + +it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. + +Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have +guessed we are adding some extra rules. + +Lets install the ruleset with composer +```shell +composer require --dev mnapoli/hard-mode +``` + +and add a configuration file to actually use it '.phpcs.xml.dist' +```xml + + + + + src + + + +``` + +running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. + +``` +[vagrant@archlinux app]$ ./vendor/bin/phpcs + +FILE: src/Bootstrap.php +---------------------------------------------------------------------------------------------------- +FOUND 4 ERRORS AFFECTING 4 LINES +---------------------------------------------------------------------------------------------------- + 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. + 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead +---------------------------------------------------------------------------------------------------- +PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY +---------------------------------------------------------------------------------------------------- + +Time: 639ms; Memory: 10MB +``` + +You can then use `./vendor/bin/phpcbf` to try to fix them + + +#### Symfony Var-Dumper + +another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small +functions. + +dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects +or arrays. + +dd() on the other hand is a function that dumps its parameters and then exits the php-script. + +you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. + +#### Composer scripts + +now we have a few commands that are available on the command line. i personally do not like to type complex commands +with lots of parameters by hand all the time, so i added a few lines to my composer.json: + +```json +"scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" +}, +``` + +that way i can just type "composer" followed by the command name in the root of my project. if i want to start the +php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the +time. + +You could also configure PhpStorm to automatically run these commands in the background and highlight the violations +directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my +flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. + +My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before +commiting and pushing the code. + +[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/implementation/16-caching/data/pages/05-http.md b/implementation/16-caching/data/pages/05-http.md new file mode 100644 index 0000000..6166214 --- /dev/null +++ b/implementation/16-caching/data/pages/05-http.md @@ -0,0 +1,124 @@ +[<< previous](04-development-helpers.md) | [next >>](06-router.md) + +### HTTP + +PHP already has a few things built in to make working with HTTP easier. For example there are the +[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. + +These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, +if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, +then you will want a class with a nice object-oriented interface that you can use in your application instead. + +Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The +standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php +projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. + +As this is a widely adopted standard there are already several implementations available for us to use. I will choose +the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. + +Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a +[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. + +Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use +that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding +the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). + + +to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit +enter + +Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): + +```php +$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); +$response = new \Laminas\Diactoros\Response; +$response->getBody()->write('Hello World! '); +$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); +``` + +This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. + +In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the +write()-Method on that object. + + +To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: + +```php +echo $response->getBody(); +``` + +This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only +stores data. + +You can play around with the other methods of the Request object and take a look at its content with the dd() function. + +```php +dd($response) +``` + +Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot +be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which +creates a copy of the request object with the changed property and returns that clone: + +```php +$response = $response->withStatus(200); +$response = $response->withAddedHeader('Content-type', 'application/json'); +``` + +If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been +defined this way. + +But if you have been keeping attention you might argue that the following line should not work if the request object is +immutable. + +```php +$response->getBody()->write('Hello World!'); +``` + +The response-body implements a stream interface which is immutable for some reasons that are described in the +[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be +aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in +the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. +I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content +to a response object. + +```php +$body = $response->getBody(); +$body->write('Hello World!'); +$response = $response->withBody($body); +``` + +Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our +output-logic a little bit more. Replace the line that echos the response-body with the following: + +```php +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); +``` + +This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a +webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. + +Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. + +Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter + + +[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/implementation/16-caching/data/pages/06-router.md b/implementation/16-caching/data/pages/06-router.md new file mode 100644 index 0000000..6c39ae5 --- /dev/null +++ b/implementation/16-caching/data/pages/06-router.md @@ -0,0 +1,101 @@ +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) + +### Router + +A router dispatches to different handlers depending on rules that you have set up. + +With your current setup it does not matter what URL is used to access the application, it will always result in the same +response. So let's fix that now. + +I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own +favorite package. + +Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) + +By now you know how to install Composer packages, so I will leave that to you. + +Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. + +```php +$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +switch ($routeInfo[0]) { + case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: + $response = (new \Laminas\Diactoros\Response)->withStatus(405); + $response->getBody()->write('Method not allowed'); + $response = $response->withStatus(405); + break; + case \FastRoute\Dispatcher::FOUND: + $handler = $routeInfo[1]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + /** @var \Psr\Http\Message\ResponseInterface $response */ + $response = call_user_func($handler, $request); + break; + case \FastRoute\Dispatcher::NOT_FOUND: + default: + $response = (new \Laminas\Diactoros\Response)->withStatus(404); + $response->getBody()->write('Not Found!'); + break; +} +``` + +In the first part of the code, you are registering the available routes for your application. In the second part, the +dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, +we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. +If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. + +This setup might work for really small applications, but once you start adding a few routes your bootstrap file will +quickly get cluttered. So let's move them out into a separate file. + +Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; + +```php +addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}; +``` + +Now let's rewrite the route dispatcher part to use the `Routes.php` file. + +```php +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); +``` + +This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. + +Of course we now need to add the 'config' folder to the configuration files of our +devhelpers so that they can scan that directory as well. + +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/implementation/16-caching/data/pages/07-dispatching-to-a-class.md b/implementation/16-caching/data/pages/07-dispatching-to-a-class.md new file mode 100644 index 0000000..0c961a4 --- /dev/null +++ b/implementation/16-caching/data/pages/07-dispatching-to-a-class.md @@ -0,0 +1,137 @@ +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) + +### Dispatching to a Class + +In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). +MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to +learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) +and the followup posts. + +So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). + +We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other +common names are 'Controllers' or 'Actions'. + +Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. +In there, create a `Hello.php` file. + +```php +getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + } +} +``` + +You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) +that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is +fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement +it in some other parts of our application as well. In order to use that Interface we have to require it with composer: +'composer require psr/http-server-handler'. + +The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. +At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. + +Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: + +```php +return function(\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); + $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); +}; +``` + +Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared +the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing +the response to the one we defined for the second route. + +To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: + +```php +case \FastRoute\Dispatcher::FOUND: + $handler = new $routeInfo[1]; + if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { + throw new \Exception('Invalid Requesthandler'); + } + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + assert($response instanceof \Psr\Http\Message\ResponseInterface) + break; +``` + +So instead of just calling a method you are now instantiating an object and then calling the method on it. + +Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. + +And of course don't forget to commit your changes. + +Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still +generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for +example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still +have our whoopsie error-handler but i like to be more explicit in my control flow. + +In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new +Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and +InternalServerError. All three should extend phps Base Exception class. + +Here is my NotFound.php for example. + +```php + $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody()->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} +``` + +Check if our code still works, try to trigger some errors, run phpstan and the fix command +and don't forget to commit your changes. + +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/implementation/16-caching/data/pages/08-inversion-of-control.md b/implementation/16-caching/data/pages/08-inversion-of-control.md new file mode 100644 index 0000000..21f4f23 --- /dev/null +++ b/implementation/16-caching/data/pages/08-inversion-of-control.md @@ -0,0 +1,54 @@ +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) + +### Inversion of Control + +In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we +want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then +we would need to edit every one of our request handlers to call a different constructor of the class. + +The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that +instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done +with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). + +If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is +implemented, it will make sense. + +Change your `Hello` action to the following: + +```php +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: + +```php +$handler = new $className($response); +``` + +Of course we need to also update all the other handlers. + +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/implementation/16-caching/data/pages/09-dependency-injector.md b/implementation/16-caching/data/pages/09-dependency-injector.md new file mode 100644 index 0000000..7f7c6a2 --- /dev/null +++ b/implementation/16-caching/data/pages/09-dependency-injector.md @@ -0,0 +1,213 @@ +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) + +### Dependency Injector + +A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when +the class is instantiated. + +Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work +with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look +for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). + +I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) +out of the box. + +After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: + +```php +addDefinitions([ + \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), +]); + +return $builder->build(); +``` + +In this file we create a containerbuilder, add some definitions to it and return the container. +As the container supports autowiring we only need to define services where we want to use a specific implementation of +an interface. + +In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to +tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second +exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. + +Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), +as we will use that extensively. + +Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally +to create our Handler-Object + +```php +$container = require __DIR__ . '/../config/dependencies.php'; +assert($container instanceof \Psr\Container\ContainerInterface); + +$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); +assert($request instanceof \Psr\Http\Message\ServerRequestInterface); +``` + +The other part that has to be changed is the dispatching of the route. Before you had the following code: + +```php +$className = $routeInfo[1]; +$handler = new $className($response); +assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Change that to the following: + +```php +/** @var RequestHandlerInterface $handler */ +$className = $routeInfo[1]; +$handler = $container->get($className); +assert($handler instanceof RequestHandlerInterface); +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Make sure to use the container fetch the response object in the catch blocks as well: + +```php +} catch (MethodNotAllowed) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(404); + $response->getBody()->write('Not Found'); +} +``` + +Now all your controller constructor dependencies will be automatically resolved with PHP-DI. + +We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons +in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call +`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we +want to work with a different date in a test. + +Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation +that creates us a DateTimeImmutable object with the current date and time. + +src/Service/Time/Now.php: +```php +namespace Lubian\NoFramework\Service\Time; + +interface Now +{ + public function __invoke(): \DateTimeImmutable; +} +``` +src/Service/Time/SystemClockNow.php: +```php +namespace Lubian\NoFramework\Service\Time; + +final class SystemClockNow implements Now +{ + + public function __invoke(): \DateTimeImmutable + { + return new \DateTimeImmutable; + } +} +``` +If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and +update the handle-method to use the new class property: + +```php +getAttribute('name', 'Stranger'); + $nowAsString = ($this->now)()->format('H:i:s'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowAsString); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI +automatically figures out what classes are requested in the constructor and tries to create the objects needed. + +But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred +S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: + +```php + public function __construct( + private ResponseInterface $response, + private Now $now, + ) +``` + +When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation +should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: + +```php +\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), +``` + +we could also use the PHP-DI create method to delegate the object creation to the container implementation: +```php +\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), +``` + +this way the container can try to resolve any dependencies that the class might have internally, but prefer the other +method because we are not depending on this specific dependency injection implementation. + +Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are +requesting the Hello action. + +If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As +we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way +we wont get any warnings for this particular issue, but for any other issues we add to our code. + +Update the phpstan.neon file to include a "baseline" file: + +``` +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` + +if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and +ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just +'baseline' + +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/implementation/16-caching/data/pages/10-invoker.md b/implementation/16-caching/data/pages/10-invoker.md new file mode 100644 index 0000000..3033fae --- /dev/null +++ b/implementation/16-caching/data/pages/10-invoker.md @@ -0,0 +1,102 @@ +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) + +### Invoker + +Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the +one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long +with some services and not the whole Requestobject with all its various properties. + +If we take our Hello action for example we only need a response object, the time service and the 'name' information from +the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named +the class hello and it would be redundant to also call the the method hello. So an updated version of that class could +look like this: + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + string $name = 'Stranger', + ): ResponseInterface + { + $body = $this->response->getBody(); + $nowString = $now->get()->format('H:i:s'); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowString); + return $response + ->withBody($body) + ->withStatus(200); + } +} +``` + +It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short +closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the +rootpath of our application yet. + +```php +$r->addRoute('GET', '/hello[/{name}]', Hello::class); +$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); +$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +``` + +In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the +route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that +class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what +arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router +if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple +callable we would need to manually use reflection as well to resolve all the arguments. + +But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) +which handles all of that for us. + +After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo +switch section in the Bootstrap.php file to use the container->call() method: + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2]; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$response = $container->call($handler, $args); +``` + +Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. + +But by now you should know that I do not like to depend on specific implementations and the call method is not defined in +the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container +or any other implementation. + +Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific +container implementation so we could use it with any container that implements the ContainerInterface. And best of all +the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that +we could implement if we ever want to write our own implementation or we could write an adapter that uses a different +class that solves the same problem. + +But for now we are using the solution provided by PHP-DI. +So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2] ?? []; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$invoker = $container->get(InvokerInterface::class); +assert($invoker instanceof InvokerInterface); +$response = $invoker->call($handler, $args); +assert($response instanceof ResponseInterface); +``` + +Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) +by php, and even some more. + +But let us move on to something more fun and add some templating functionality to our application as we are trying to build +a website in the end. + +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/implementation/16-caching/data/pages/11-templating.md b/implementation/16-caching/data/pages/11-templating.md new file mode 100644 index 0000000..3759664 --- /dev/null +++ b/implementation/16-caching/data/pages/11-templating.md @@ -0,0 +1,240 @@ +[<< previous](10-invoker.md) | [next >>](12-configuration.md) + +### Templating + +A template engine is not necessary with PHP because the language itself can take care of that. But it can make things +like escaping values easier. They also make it easier to draw a clear line between your application logic and the +template files which should only put your variables into the HTML code. + +A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please +also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. +Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. + +For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install +that package before you continue (`composer require mustache/mustache`). + +Another well known alternative would be [Twig](http://twig.sensiolabs.org/). + +Now please go and have a look at the source code of the +[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class +does not implement an interface. + +You could just type hint against the concrete class. But the problem with this approach is that you create tight +coupling. + +In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the +implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want +to add functionality to the engine. You can't do that without going back and changing all your code that is tightly +coupled. + +What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need +another implementation, you just implement that interface in your new class and inject the new class instead. + +Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). +This sounds a lot more complicated than it is, so just follow along. + +First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). +This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. +A class can implement multiple interfaces if necessary. + +So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a +new folder in your `src/` folder with the name `Template` where you can put all the template related things. + +In there create a new interface `Renderer.php` that looks like this: + +```php + $data + * @return string + */ + public function render(string $template, array $data = []) : string; +} +``` + +Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file +`MustacheRenderer.php` with the following content: + +```php +engine->render($template, $data); + } +} +``` + +As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple +and only fulfills the interface. + +Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know +which implementation he has to inject when you hint for the interface. Add this line: + +```php +[ + ... + \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) + ->constructor(new Mustache_Engine), +] +``` + +Now update the Hello.php class to require an implementation of our renderer interface +and use that to render a string using mustache syntax. + + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + Renderer $renderer, + string $name = 'Stranger', + ): ResponseInterface { + $body = $response->getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render( + 'Hello {{name}}, the time is {{now}}!', + $data, + ); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} +``` + +Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. +But what we want is template files, so let's go back and change that. + +To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the +`dependencies.php` file and add the following code: + +```php +[ + ... + Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), + Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), +] +``` + +We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. +Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have +to rename all the template files. + +To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the +dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader +in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object +we can then use it in the factory. + +In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: + +``` +

Hello World

+Hello {{ name }} +``` + +Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` + +Navigate to the hello page in your browser to make sure everything works. + +One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies +file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration +values. + +Lets create a 'Settings' class in our './src' Folder: + +```php +addDefinitions([ + Settings::class => fn () => require __DIR__ '/settings.php', + ResponseInterface::class => create(Response::class), + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + Renderer::class => fn (ME $me) => new Mustache($me), + MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), +]); + +return $builder->build(); +``` + + + +And as always, don't forget to commit your changes. + + +[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/implementation/16-caching/data/pages/12-configuration.md b/implementation/16-caching/data/pages/12-configuration.md new file mode 100644 index 0000000..a44dfd5 --- /dev/null +++ b/implementation/16-caching/data/pages/12-configuration.md @@ -0,0 +1,201 @@ +[<< previous](11-templating.md) | [next >>](13-refactoring.md) + +### Configuration + +In the last chapter we added some more definitions to our dependencies.php in that definitions +we needed to pass quite a few configuration settings and filesystem strings to the constructors +of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. + +As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. + +As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. + +Lets create a 'Settings' class in our './src' Folder: + +```php +filePath; + } +} +``` + +If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files +and craft a settings object from them. + +As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another +factory for our Container because everyone should have a Friend :) + +```php +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn () => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} +``` + +For this to work we need to change our dependencies.php file to just return the array of definitions: +And here we can instantly use the Settings object to create our template engine. + +```php + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), +]; +``` + +Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: +require __DIR__ . '/../vendor/autoload.php'; + +```php +... +error_reporting(E_ALL); + +$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); +$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); +... +``` + +Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. + +[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/implementation/16-caching/data/pages/13-refactoring.md b/implementation/16-caching/data/pages/13-refactoring.md new file mode 100644 index 0000000..067e168 --- /dev/null +++ b/implementation/16-caching/data/pages/13-refactoring.md @@ -0,0 +1,377 @@ +[<< previous](12-configuration.md) | [next >>](14-middleware.md) + +### Refactoring + +By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no +reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. +After all the bootstrap file should just set up the classes needed for the handling logic and execute them. + +At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. +As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the +method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non +static and extracted an interface. + +'./src/Http/Emitter.php' +```php +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + echo $response->getBody(); + } +} +``` +After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following +code in the Bootstrap.php to emit the response: + +```php +/** @var Emitter $emitter */ +$emitter = $container->get(Emitter::class); +$emitter->emit($response); +``` + +If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily +write an adapter that implements your emitter interface and wraps that more advanced emitter + +Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and +calling the routerhandler that in the passes the request to a function and gets the response. + +For this to steps to be seperated we are going to create two more classes: +1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object +2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the + requestobject, fetches the correct object from the container and calls it to create a response. + +Lets create the HandlerInterface first: + +```php +getAttribute($this->routeAttributeName, false); + assert($handler !== false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} + +``` + +We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. +The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler +as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in +the next chapter. + +```php +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} +``` + +Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. +```php +[ + '...', + Emitter::class => fn (BasicEmitter $e) => $e, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, +], +``` + +And then we can update our Bootstrap.php to fetch all the services and let them handle the request. + +```php +... +$routeMiddleWare = $container->get(MiddlewareInterface::class); +assert($routeMiddleWare instanceof MiddlewareInterface); +$handler = $container->get(RoutedRequestHandler::class); +assert($handler instanceof RequestHandlerInterface); +$emitter = $container->get(Emitter::class); +assert($emitter instanceof Emitter); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$response = $routeMiddleWare->process($request, $handler); +$emitter->emit($response); +``` +Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of +code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). + +So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. + +I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class +should require to function properly. + +* A RequestFactory + We want our Kernel to be able to build the request itself +* An Emitter + Without an Emitter we will not be able to send the response to the client +* RouteMiddleware + To decore the request with the correct handler for the requested route +* RequestHandler + To delegate the request to the correct funtion that creates the response + +As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface +to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our +interface: + +```php +factory::fromGlobals(); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} +``` + +For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: + +```php +routeMiddleware->process($request, $this->handler); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} + +``` + +We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines + +```php +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); +``` + +You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking +at the Whoops output and adding the needed definitions to the dependencies.php file. + +And as always, don't forget to commit your changes. + +[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/implementation/16-caching/data/pages/14-middleware.md b/implementation/16-caching/data/pages/14-middleware.md new file mode 100644 index 0000000..e698327 --- /dev/null +++ b/implementation/16-caching/data/pages/14-middleware.md @@ -0,0 +1,298 @@ +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) + +### Middleware + +In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain +a bit more about what this interface does and why it is used in many applications. + +The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets +passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all +the middlewars to the client/emitter. + +So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the +response after it gets created by our handlers. + +So lets take a look at the middleware and the requesthandler interfaces + +```php +interface MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; +} + +interface RequestHandlerInterface +{ + public function handle(ServerRequestInterface $request): ResponseInterface; +} +``` + +The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a +requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the +response. + +But the middleware could just ignore the handler and produce a response on its own as the interface just requires us +to produce a response. + +A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users +that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the +database. + +In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates +the request with an 'isAuthenticated' attribute. + +If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that +response is not already cached, than we let the handler create the response and store that in the cache for a few +seconds + +```php +interface CacheInterface +{ + public function get(string $key, callable $resolver, int $ttl): mixed; +} +``` + +The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one +defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses +the callable to produce the value and stores it for the time specified in ttl. + +so lets write our caching middleware: + +```php +final class CachingMiddleware implements MiddlewareInterface +{ + public function __construct(private CacheInterface $cache){} + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { + $key = $request->getUri()->getPath(); + return $this->cache->get($key, fn() => $handler->handle($request), 10); + } + return $handler->handle($request); + } +} +``` + +we can also modify the response after it has been created by our application, for example we could implement a gzip +middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser +know that our application is used to serve dank memes: + +```php +final class DankMemeMiddleware implements MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $response = $handler->handle($request); + return $response->withAddedHeader('Meme', 'Dank'); + } +} +``` + +but for our application we are going to just add two external middlewares: + +* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. +* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware + +```bash +composer require middlewares/trailing-slash +composer require middlewares/whoops +``` + +The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the +application as well as the middleware stack. + +Our desired request -> response flow looks something like this: + + Client + | ^ + v | + Kernel + | ^ + v | + Whoops Middleware + | ^ + v | + TrailingSlash + | ^ + v | + Routing + | ^ + v | + ContainerResolver + | ^ + v | + Controller/Action + +As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every +middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. + +```php +interface Pipeline +{ + public function dispatch(ServerRequestInterface $request): ResponseInterface; +} +``` + +And our implementation looks something like this: + +```php + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} +``` + +Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code +that should produce our response. + +In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument +and store that itself as the current tip. + +There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) + +Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline +Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline + +```php +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} +``` + +And configure the container to use the Factory to create the Pipeline: + +```php + ..., + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + ... +``` +And of course a new file called middlewares.php in our config folder: +```php +pipeline->dispatch($request); +} +``` + +Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our +pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a +dependency container, if you want you can do that as well. + +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) diff --git a/implementation/16-caching/phpstan-baseline.neon b/implementation/16-caching/phpstan-baseline.neon new file mode 100644 index 0000000..61697a1 --- /dev/null +++ b/implementation/16-caching/phpstan-baseline.neon @@ -0,0 +1,7 @@ +parameters: + ignoreErrors: + - + message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" + count: 1 + path: src/Http/InvokerRoutedHandler.php + diff --git a/implementation/16-caching/phpstan.neon b/implementation/16-caching/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/16-caching/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/16-caching/public/css/spectre-exp.min.css b/implementation/16-caching/public/css/spectre-exp.min.css new file mode 100644 index 0000000..d313774 --- /dev/null +++ b/implementation/16-caching/public/css/spectre-exp.min.css @@ -0,0 +1 @@ +/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/implementation/16-caching/public/css/spectre-icons.min.css b/implementation/16-caching/public/css/spectre-icons.min.css new file mode 100644 index 0000000..0276f7b --- /dev/null +++ b/implementation/16-caching/public/css/spectre-icons.min.css @@ -0,0 +1 @@ +/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/implementation/16-caching/public/css/spectre.min.css b/implementation/16-caching/public/css/spectre.min.css new file mode 100644 index 0000000..0fe23d9 --- /dev/null +++ b/implementation/16-caching/public/css/spectre.min.css @@ -0,0 +1 @@ +/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/implementation/16-caching/public/favicon.ico b/implementation/16-caching/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/16-caching/public/index.php b/implementation/16-caching/public/index.php new file mode 100644 index 0000000..d93da3a --- /dev/null +++ b/implementation/16-caching/public/index.php @@ -0,0 +1,5 @@ +getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render('hello', $data); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/16-caching/src/Action/Other.php b/implementation/16-caching/src/Action/Other.php new file mode 100644 index 0000000..895796e --- /dev/null +++ b/implementation/16-caching/src/Action/Other.php @@ -0,0 +1,19 @@ +getBody(); + + $body->write('This works too!'); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/16-caching/src/Action/Page.php b/implementation/16-caching/src/Action/Page.php new file mode 100644 index 0000000..e3461ad --- /dev/null +++ b/implementation/16-caching/src/Action/Page.php @@ -0,0 +1,35 @@ +byTitle($page); + $content = $this->linkFilter($page->content); + $content = $parsedown->parse($content); + $html = $renderer->render('page', ['content' => $content, 'title' => $page->title]); + $response->getBody()->write($html); + return $response; + } + + private function linkFilter(string $content): string + { + $content = preg_replace('/\(\d\d-/m', '(', $content); + return str_replace('.md)', ')', $content); + } +} diff --git a/implementation/16-caching/src/Bootstrap.php b/implementation/16-caching/src/Bootstrap.php new file mode 100644 index 0000000..3abc2e5 --- /dev/null +++ b/implementation/16-caching/src/Bootstrap.php @@ -0,0 +1,40 @@ +getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); diff --git a/implementation/16-caching/src/Exception/InternalServerError.php b/implementation/16-caching/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/16-caching/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +factory::fromGlobals(); + } + + /** + * @param UriInterface|string $uri + * @param array $serverParams + */ + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} diff --git a/implementation/16-caching/src/Factory/FileSystemSettingsProvider.php b/implementation/16-caching/src/Factory/FileSystemSettingsProvider.php new file mode 100644 index 0000000..f071078 --- /dev/null +++ b/implementation/16-caching/src/Factory/FileSystemSettingsProvider.php @@ -0,0 +1,22 @@ +filePath; + assert($settings instanceof Settings); + return $settings; + } +} diff --git a/implementation/16-caching/src/Factory/PipelineProvider.php b/implementation/16-caching/src/Factory/PipelineProvider.php new file mode 100644 index 0000000..77738f8 --- /dev/null +++ b/implementation/16-caching/src/Factory/PipelineProvider.php @@ -0,0 +1,25 @@ +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} diff --git a/implementation/16-caching/src/Factory/RequestFactory.php b/implementation/16-caching/src/Factory/RequestFactory.php new file mode 100644 index 0000000..2b17abc --- /dev/null +++ b/implementation/16-caching/src/Factory/RequestFactory.php @@ -0,0 +1,11 @@ +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn (): Settings => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} diff --git a/implementation/16-caching/src/Factory/SettingsProvider.php b/implementation/16-caching/src/Factory/SettingsProvider.php new file mode 100644 index 0000000..ce1c5f0 --- /dev/null +++ b/implementation/16-caching/src/Factory/SettingsProvider.php @@ -0,0 +1,10 @@ +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + + echo $response->getBody(); + } +} diff --git a/implementation/16-caching/src/Http/ContainerPipeline.php b/implementation/16-caching/src/Http/ContainerPipeline.php new file mode 100644 index 0000000..816cedd --- /dev/null +++ b/implementation/16-caching/src/Http/ContainerPipeline.php @@ -0,0 +1,82 @@ + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} diff --git a/implementation/16-caching/src/Http/Emitter.php b/implementation/16-caching/src/Http/Emitter.php new file mode 100644 index 0000000..ce4c035 --- /dev/null +++ b/implementation/16-caching/src/Http/Emitter.php @@ -0,0 +1,10 @@ +getAttribute($this->routeAttributeName, false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} diff --git a/implementation/16-caching/src/Http/Pipeline.php b/implementation/16-caching/src/Http/Pipeline.php new file mode 100644 index 0000000..1a9dcda --- /dev/null +++ b/implementation/16-caching/src/Http/Pipeline.php @@ -0,0 +1,11 @@ +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} diff --git a/implementation/16-caching/src/Http/RoutedRequestHandler.php b/implementation/16-caching/src/Http/RoutedRequestHandler.php new file mode 100644 index 0000000..a7407c9 --- /dev/null +++ b/implementation/16-caching/src/Http/RoutedRequestHandler.php @@ -0,0 +1,10 @@ +pipeline->dispatch($request); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} diff --git a/implementation/16-caching/src/Middleware/CacheMiddleware.php b/implementation/16-caching/src/Middleware/CacheMiddleware.php new file mode 100644 index 0000000..edf67d7 --- /dev/null +++ b/implementation/16-caching/src/Middleware/CacheMiddleware.php @@ -0,0 +1,36 @@ +getMethod() === 'GET') { + $key = (string) $request->getUri(); + $key = base64_encode($key); + $callback = fn () => $handler->handle($request); + $cached = $this->cache->get($key, function (ItemInterface $item) use ($callback) { + $item->expiresAfter(120); + $response = $callback(); + return $this->serializer::toString($response); + }); + return $this->serializer::fromString($cached); + } + return $handler->handle($request); + } +} diff --git a/implementation/16-caching/src/Model/MarkdownPage.php b/implementation/16-caching/src/Model/MarkdownPage.php new file mode 100644 index 0000000..503774f --- /dev/null +++ b/implementation/16-caching/src/Model/MarkdownPage.php @@ -0,0 +1,13 @@ + $this->repo->all(); + return $this->cache->get('ALLPAGES', function (ItemInterface $item) use ($callback) { + $item->expiresAfter(30); + return $callback(); + }); + } + + public function byId(int $id): MarkdownPage + { + $callback = fn () => $this->repo->byId($id); + return $this->cache->get('PAGE' . $id, function (ItemInterface $item) use ($callback) { + $item->expiresAfter(30); + return $callback(); + }); + } + + public function byTitle(string $title): MarkdownPage + { + $callback = fn () => $this->repo->byTitle($title); + return $this->cache->get('PAGE' . $title, function (ItemInterface $item) use ($callback) { + $item->expiresAfter(30); + return $callback(); + }); + } +} diff --git a/implementation/16-caching/src/Repository/MarkdownPageFilesystem.php b/implementation/16-caching/src/Repository/MarkdownPageFilesystem.php new file mode 100644 index 0000000..abd4107 --- /dev/null +++ b/implementation/16-caching/src/Repository/MarkdownPageFilesystem.php @@ -0,0 +1,63 @@ +dataPath . '*.md'); + assert(is_array($fileNames)); + return array_map(function (string $name): MarkdownPage { + usleep(100000); + $content = file_get_contents($name); + $name = str_replace($this->dataPath, '', $name); + $name = str_replace('.md', '', $name); + $id = (int) substr($name, 0, 2); + $title = substr($name, 3); + return new MarkdownPage($id, $title, $content); + }, $fileNames); + } + + public function byId(int $id): MarkdownPage + { + $callback = fn (MarkdownPage $p): bool => $p->id === $id; + $filtered = array_values(array_filter($this->all(), $callback)); + if (count($filtered) === 0) { + throw new NotFound; + } + return $filtered[0]; + } + + public function byTitle(string $title): MarkdownPage + { + $callback = fn (MarkdownPage $p): bool => $p->title === $title; + $filtered = array_values(array_filter($this->all(), $callback)); + if (count($filtered) === 0) { + throw new NotFound; + } + return $filtered[0]; + } +} diff --git a/implementation/16-caching/src/Repository/MarkdownPageRepo.php b/implementation/16-caching/src/Repository/MarkdownPageRepo.php new file mode 100644 index 0000000..b823af0 --- /dev/null +++ b/implementation/16-caching/src/Repository/MarkdownPageRepo.php @@ -0,0 +1,17 @@ +engine->render($template, $data); + } +} diff --git a/implementation/16-caching/src/Template/Renderer.php b/implementation/16-caching/src/Template/Renderer.php new file mode 100644 index 0000000..ff916ed --- /dev/null +++ b/implementation/16-caching/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data = []): string; +} diff --git a/implementation/16-caching/templates/hello.html b/implementation/16-caching/templates/hello.html new file mode 100644 index 0000000..15a4cd2 --- /dev/null +++ b/implementation/16-caching/templates/hello.html @@ -0,0 +1,6 @@ +{{> partials/head }} +
+

Hello {{name}}

+

The time is {{now}}

+
+{{> partials/foot }} diff --git a/implementation/16-caching/templates/page.html b/implementation/16-caching/templates/page.html new file mode 100644 index 0000000..c3c5284 --- /dev/null +++ b/implementation/16-caching/templates/page.html @@ -0,0 +1,5 @@ +{{> partials/head }} +
+ {{{content}}} +
+{{> partials/foot }} diff --git a/implementation/16-caching/templates/partials/foot.html b/implementation/16-caching/templates/partials/foot.html new file mode 100644 index 0000000..17c7245 --- /dev/null +++ b/implementation/16-caching/templates/partials/foot.html @@ -0,0 +1,3 @@ +
+ + \ No newline at end of file diff --git a/implementation/16-caching/templates/partials/head.html b/implementation/16-caching/templates/partials/head.html new file mode 100644 index 0000000..421d387 --- /dev/null +++ b/implementation/16-caching/templates/partials/head.html @@ -0,0 +1,11 @@ + + + + + No Framework: {{title}} + + + + + +
From 11172fb3d31b4d5abd0351138d4e170ae6278409 Mon Sep 17 00:00:00 2001 From: lubiana Date: Tue, 5 Apr 2022 19:09:40 +0200 Subject: [PATCH 278/314] add 'adding content' chapter --- 14-middleware.md | 4 +- 15-adding-content.md | 248 ++++++++++ app/composer.json | 3 +- app/composer.lock | 460 +++++++++++++++++- app/config/dependencies.php | 4 +- app/config/middlewares.php | 2 - app/src/Action/Other.php | 13 +- app/src/Action/Page.php | 69 ++- app/src/Factory/DoctrineEm.php | 12 +- app/src/Factory/SettingsContainerProvider.php | 2 +- app/src/Middleware/CacheMiddleware.php | 5 +- app/src/Model/MarkdownPage.php | 10 +- .../Repository/DoctrineMarkdownPageRepo.php | 27 +- app/src/Repository/MarkdownPageFilesystem.php | 3 +- app/src/Template/GithubMarkdownRenderer.php | 28 ++ app/src/Template/MarkdownParser.php | 8 + app/src/Template/ParsedownParser.php | 17 + app/templates/page/list.html | 19 + app/templates/page/show.html | 13 + implementation/02-composer/composer.lock | 188 +------ 20 files changed, 884 insertions(+), 251 deletions(-) create mode 100644 15-adding-content.md create mode 100644 app/src/Template/GithubMarkdownRenderer.php create mode 100644 app/src/Template/MarkdownParser.php create mode 100644 app/src/Template/ParsedownParser.php create mode 100644 app/templates/page/list.html create mode 100644 app/templates/page/show.html diff --git a/14-middleware.md b/14-middleware.md index e698327..087e26b 100644 --- a/14-middleware.md +++ b/14-middleware.md @@ -1,4 +1,4 @@ -[<< previous](12-refactoring.md) | [next >>](14-invoker.md) +[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) ### Middleware @@ -295,4 +295,4 @@ Lets try if you can make the kernel work with our created Pipeline implementatio pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a dependency container, if you want you can do that as well. -[<< previous](12-refactoring.md) | [next >>](14-invoker.md) +[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) diff --git a/15-adding-content.md b/15-adding-content.md new file mode 100644 index 0000000..d1a7348 --- /dev/null +++ b/15-adding-content.md @@ -0,0 +1,248 @@ +[<< previous](14-middleware.md) | [next >>](14-invoker.md) + +### Adding Content + +By now we did not really display anything but some examples to in our application and it is now time to make our app +display some content. For example we could our app be able to display the Markdown files used in this tutorial as +nicely rendered HTML Pages that can be viewed in the browser instead of the editor you are using. + +So lets start by copying the markdown files to our app directory. I have created a new folder 'data/pages' and placed all +the markdown files in there. + +Next we need a markdown parser, a pretty simple one is [Parsedown](https://parsedown.org/), if you want more features +you could also use the [Commonmark parser](https://commonmark.thephpleague.com/). I will choose Parsedown here, but you +can use whatever you like. + +After installing Parsedown lets write a Markdownparser interface and an implementation using parsedown. + +We only need one function that receives a string of Markdown and returns the HTML represantion (as a string as well). + + + +```php +parser->parse($markdown); + } +} +``` + +We could now use the ParsedownRender class directly in our actions by typehinting the classname as an argument to the +constructor or a method, but as we always want to rely on an interface instead of an implementation we need to define +the the ParsedownRenderer as the correct implementation for the MarkdownRenderer interface in the dependencies file: + +```php +... + \Lubian\NoFramework\Template\MarkdownParser::class => fn(\Lubian\NoFramework\Template\ParsedownParser $p) => $p, +... +``` + +You can test that in our "Other.php" action and try out if the Parser works and is able to render Markdown to HTML: + +```php +public function someFunctionName(ResponseInterface $response, MarkdownParser $parser): ResponseInterface +{ + $html = $parser->parse('This *works* **too!**'); + $response->getBody()->write($html); + return $response->withStatus(200); +} +``` + +But we want to display complete Pages written in Markdown, it would also be neat to be able to display a list of all +available pages. For that we need a few things: + +Firstly we need two new Templates, one for the list of the Pages, and the second one for displaying a single pages +content. Create a new folder in `templates/page` with to files: + +`templates/page/list.html` +```html + + + + + Pages + + + +
+ +
+ + +``` + +This template iterates over a provided array of pages, each element consists of the two properties: an id and a title, +those are simply displayed using an unordered list. + +`templates/page/show.html` +```html + + + + + {{title}} + + + +
+ {{{content}}} +
+ + +``` + +The second templates displays a single rendered markdown page. As data it expects the title and the content as array. +I used an extra bracket for the content ```{{{content}}}``` so that the Mustache-Renderer does not escape the provided +html and thereby destroys the the parsed markdown. + +You might have spotted that I added [Pico.css](https://picocss.com/) which is just a very small css framework to make the +pages a little bit nicer to look at. It mostly provides some typography styles that work great with rendered Markdown, +but you can leave that out or use any other css framework you like. + +After you have taken care of the templating side we can now create an new Action class with two methods to display use +our markdown files and the templates to create the pages. As we have two templates I propose to use Two methods in our +Action: +`src/Action/Page.php` +```php +function show(string $name): \Psr\Http\Message\ResponseInterface; +function list(): \Psr\Http\Message\ResponseInterface; +``` + +Lets define two routes. `/page` should display the overview of all pages, and if the add the name of chapter to the +route, `/page/adding-content` for example, the show action should be called with the name as a variable: + +`config/routes.php` +```php +$r->addRoute('GET', '/page', [Page::class, 'list']); +$r->addRoute('GET', '/page/{page}', [Page::class, 'show']); +``` + +Here is my Implementation. If have added a little regex replacement in the show method that replaces the links to the +next and previous chapter so that it works with our routing configuration. + +`src/Action/Page.php` +```php +getPages(), + fn (string $filename) => str_contains($filename, $page) + ) + )[0]; + $markdown = file_get_contents($page); + + // fix the next and previous buttons to work with our routing + $markdown = preg_replace('/\(\d\d-/m', '(', $markdown); + $markdown = str_replace('.md)', ')', $markdown); + + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + $data = [ + 'title' => substr($page, 3), + 'content' => $this->parser->parse($markdown), + ]; + $html = $this->renderer->render('page/show', $data); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map(function (string $page) { + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + return [ + 'id' => substr($page, 0, 2), + 'title' => substr($page, 3), + ]; + }, $this->getPages()); + $html = $this->renderer->render('page/list', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } + + /** + * @return string[] + */ + private function getPages(): array + { + $files = glob($this->pagesPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return $files; + } +} +``` + +You can now navigate your Browser to [localhost:1234/page][http://localhost:1234/page] and try out if everything works. + +Of course this code is far from looking good. We heavily rely on the pages being files in the filesystem, and the action +should never be aware of the filesystem in the first place, also we have a lot of string replacements and other repetetive +code in the file. And phpstan is gonna scream at us a lot, but if we rewrite the code to satisfy all the checks we would +add even more lines to that simple class, so lets move on to the next chapter where we move all the logic to seperate +classes following our holy SOLID principles :) + + +[<< previous](14-middleware.md) | [next >>](14-invoker.md) diff --git a/app/composer.json b/app/composer.json index 04edde0..4809539 100644 --- a/app/composer.json +++ b/app/composer.json @@ -13,7 +13,8 @@ "middlewares/whoops": "^2.0", "erusev/parsedown": "^1.7", "symfony/cache": "^6.0", - "doctrine/orm": "^2.11" + "doctrine/orm": "^2.11", + "league/commonmark": "^2.2" }, "autoload": { "psr-4": { diff --git a/app/composer.lock b/app/composer.lock index 904cfb4..648f2d5 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -4,8 +4,83 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a3762dd11bab0c9e948d3a73b7f252b9", + "content-hash": "1e8469bfebe6479a139b946b8aba49de", "packages": [ + { + "name": "dflydev/dot-access-data", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^3.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" + }, + "time": "2021-08-13T13:06:58+00:00" + }, { "name": "doctrine/cache", "version": "2.1.1", @@ -1155,6 +1230,192 @@ ], "time": "2022-03-29T20:12:16+00:00" }, + { + "name": "league/commonmark", + "version": "2.2.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", + "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.15" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.30.0", + "commonmark/commonmark.js": "0.30.0", + "composer/package-versions-deprecated": "^1.8", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4", + "phpstan/phpstan": "^0.12.88 || ^1.0.0", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2022-02-26T21:24:45+00:00" + }, + { + "name": "league/config", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.90", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2021-08-14T12:15:32+00:00" + }, { "name": "middlewares/trailing-slash", "version": "v2.0.1", @@ -1378,6 +1639,153 @@ }, "time": "2022-01-21T06:08:36+00:00" }, + { + "name": "nette/schema", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", + "shasum": "" + }, + "require": { + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": ">=7.1 <8.2" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "phpstan/phpstan-nette": "^0.12", + "tracy/tracy": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.2.2" + }, + "time": "2021-10-15T11:40:02+00:00" + }, + { + "name": "nette/utils", + "version": "v3.2.7", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", + "shasum": "" + }, + "require": { + "php": ">=7.2 <8.2" + }, + "conflict": { + "nette/di": "<3.0.6" + }, + "require-dev": { + "nette/tester": "~2.0", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v3.2.7" + }, + "time": "2022-01-24T11:29:14+00:00" + }, { "name": "nikic/fast-route", "version": "v1.3.0", @@ -1763,6 +2171,56 @@ }, "time": "2021-11-05T16:50:12+00:00" }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, { "name": "psr/http-factory", "version": "1.0.1", diff --git a/app/config/dependencies.php b/app/config/dependencies.php index 4a97f31..e2a3925 100644 --- a/app/config/dependencies.php +++ b/app/config/dependencies.php @@ -14,13 +14,14 @@ use Lubian\NoFramework\Http\Pipeline; use Lubian\NoFramework\Http\RoutedRequestHandler; use Lubian\NoFramework\Http\RouteMiddleware; use Lubian\NoFramework\Repository\CachedMarkdownPageRepo; -use Lubian\NoFramework\Repository\DoctrineMarkdownPageRepo; use Lubian\NoFramework\Repository\MarkdownPageFilesystem; use Lubian\NoFramework\Repository\MarkdownPageRepo; use Lubian\NoFramework\Service\Time\Now; use Lubian\NoFramework\Service\Time\SystemClockNow; use Lubian\NoFramework\Settings; +use Lubian\NoFramework\Template\MarkdownParser; use Lubian\NoFramework\Template\MustacheRenderer; +use Lubian\NoFramework\Template\ParsedownParser; use Lubian\NoFramework\Template\Renderer; use Mustache_Engine as ME; use Mustache_Loader_FilesystemLoader as MLF; @@ -43,6 +44,7 @@ return [ RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, CacheInterface::class => fn (FilesystemAdapter $a) => $a, MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, + MarkdownParser::class => fn (ParsedownParser $p) => $p, // Factories ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), diff --git a/app/config/middlewares.php b/app/config/middlewares.php index 459547e..71dd461 100644 --- a/app/config/middlewares.php +++ b/app/config/middlewares.php @@ -1,13 +1,11 @@ getBody(); - - $body->write('This works too!'); - - return $response - ->withStatus(200) - ->withBody($body); + $html = $parser->parse('This *works* **too!**'); + $response->getBody()->write($html); + return $response->withStatus(200); } } diff --git a/app/src/Action/Page.php b/app/src/Action/Page.php index 9fb86c2..6a3aad0 100644 --- a/app/src/Action/Page.php +++ b/app/src/Action/Page.php @@ -2,50 +2,79 @@ namespace Lubian\NoFramework\Action; -use Lubian\NoFramework\Model\MarkdownPage; -use Lubian\NoFramework\Repository\MarkdownPageFilesystem; -use Lubian\NoFramework\Repository\MarkdownPageRepo; +use Lubian\NoFramework\Exception\InternalServerError; +use Lubian\NoFramework\Template\MarkdownParser; use Lubian\NoFramework\Template\Renderer; -use Parsedown; use Psr\Http\Message\ResponseInterface; +use function array_filter; +use function array_map; +use function array_values; +use function file_get_contents; +use function glob; use function preg_replace; +use function str_contains; use function str_replace; +use function substr; class Page { public function __construct( private ResponseInterface $response, - private MarkdownPageRepo $repo, - private Parsedown $parsedown, + private MarkdownParser $parser, private Renderer $renderer, - ){} + private string $pagesPath = __DIR__ . '/../../data/pages/' + ) { + } + public function show( string $page, ): ResponseInterface { - $page = $this->repo->byTitle($page); - $content = $this->linkFilter($page->content); - $content = $this->parsedown->parse($content); - $html = $this->renderer->render('page', ['content' => $content, 'title' => $page->title]); + $page = array_values( + array_filter( + $this->getPages(), + fn (string $filename) => str_contains($filename, $page) + ) + )[0]; + $markdown = file_get_contents($page); + + // fix the next and previous buttons to work with our routing + $markdown = preg_replace('/\(\d\d-/m', '(', $markdown); + $markdown = str_replace('.md)', ')', $markdown); + + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + $data = [ + 'title' => substr($page, 3), + 'content' => $this->parser->parse($markdown), + ]; + $html = $this->renderer->render('page/show', $data); $this->response->getBody()->write($html); return $this->response; } public function list(): ResponseInterface { - $pages = array_map( - fn (MarkdownPage $p) => ['title' => $p->title, 'id' => $p->id], - $this->repo->all() - ); - $html = $this->renderer->render('pagelist', ['pages' => $pages]); + $pages = array_map(function (string $page) { + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + return [ + 'id' => substr($page, 0, 2), + 'title' => substr($page, 3), + ]; + }, $this->getPages()); + $html = $this->renderer->render('page/list', ['pages' => $pages]); $this->response->getBody()->write($html); return $this->response; - } - private function linkFilter(string $content): string + /** + * @return string[] + */ + private function getPages(): array { - $content = preg_replace('/\(\d\d-/m', '(', $content); - return str_replace('.md)', ')', $content); + $files = glob($this->pagesPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return $files; } } diff --git a/app/src/Factory/DoctrineEm.php b/app/src/Factory/DoctrineEm.php index be24e7f..b0be39b 100644 --- a/app/src/Factory/DoctrineEm.php +++ b/app/src/Factory/DoctrineEm.php @@ -1,7 +1,4 @@ -settings->doctrine['devMode']); @@ -29,4 +29,4 @@ final class DoctrineEm $config, ); } -} \ No newline at end of file +} diff --git a/app/src/Factory/SettingsContainerProvider.php b/app/src/Factory/SettingsContainerProvider.php index baf278b..5b89266 100644 --- a/app/src/Factory/SettingsContainerProvider.php +++ b/app/src/Factory/SettingsContainerProvider.php @@ -20,7 +20,7 @@ final class SettingsContainerProvider implements ContainerProvider $dependencies = require $settings->dependenciesFile; $dependencies[Settings::class] = $settings; $builder->addDefinitions($dependencies); - // $builder->enableCompilation('/tmp'); + // $builder->enableCompilation('/tmp'); return $builder->build(); } } diff --git a/app/src/Middleware/CacheMiddleware.php b/app/src/Middleware/CacheMiddleware.php index 2ff6f6e..1bd58c5 100644 --- a/app/src/Middleware/CacheMiddleware.php +++ b/app/src/Middleware/CacheMiddleware.php @@ -19,13 +19,12 @@ final class CacheMiddleware implements MiddlewareInterface private CacheInterface $cache, private Response\Serializer $serializer, private Settings $settings, - ) - { + ) { } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { - if ($request->getMethod() === 'GET' && !$this->settings->isDev()) { + if ($request->getMethod() === 'GET' && ! $this->settings->isDev()) { $key = (string) $request->getUri(); $key = base64_encode($key); $callback = fn () => $handler->handle($request); diff --git a/app/src/Model/MarkdownPage.php b/app/src/Model/MarkdownPage.php index 73f001d..bae383c 100644 --- a/app/src/Model/MarkdownPage.php +++ b/app/src/Model/MarkdownPage.php @@ -3,17 +3,15 @@ namespace Lubian\NoFramework\Model; use Doctrine\DBAL\Types\Types; -use Doctrine\ORM\Mapping\Column; -use Doctrine\ORM\Mapping\Entity; -use Doctrine\ORM\Mapping\GeneratedValue; -use Doctrine\ORM\Mapping\Id; #[Entity] class MarkdownPage { public function __construct( - #[Id, Column, GeneratedValue] - public int|null $id = null, + #[Id, + Column, + GeneratedValue] + public int |null $id = null, #[Column] public string $title, #[Column(type: Types::TEXT)] diff --git a/app/src/Repository/DoctrineMarkdownPageRepo.php b/app/src/Repository/DoctrineMarkdownPageRepo.php index f107c9e..8d1d457 100644 --- a/app/src/Repository/DoctrineMarkdownPageRepo.php +++ b/app/src/Repository/DoctrineMarkdownPageRepo.php @@ -1,7 +1,4 @@ - - */ + /** @var EntityRepository */ private EntityRepository $repo; + public function __construct( private EntityManagerInterface $entityManager - ){ + ) { $this->repo = $this->entityManager->getRepository(MarkdownPage::class); } @@ -27,15 +26,15 @@ final class DoctrineMarkdownPageRepo implements MarkdownPageRepo */ public function all(): array { - usleep(rand(500, 1500) * 1000); + usleep(random_int(500, 1500) * 1000); return $this->repo->findAll(); } public function byId(int $id): MarkdownPage { - usleep(rand(500, 1500) * 1000); + usleep(random_int(500, 1500) * 1000); $page = $this->repo->findOneBy(['id' => $id]); - if (!$page instanceof MarkdownPage){ + if (! $page instanceof MarkdownPage) { throw new NotFound; } return $page; @@ -43,9 +42,9 @@ final class DoctrineMarkdownPageRepo implements MarkdownPageRepo public function byTitle(string $title): MarkdownPage { - usleep(rand(500, 1500) * 1000); + usleep(random_int(500, 1500) * 1000); $page = $this->repo->findOneBy(['title' => $title]); - if (!$page instanceof MarkdownPage){ + if (! $page instanceof MarkdownPage) { throw new NotFound; } return $page; @@ -57,4 +56,4 @@ final class DoctrineMarkdownPageRepo implements MarkdownPageRepo $this->entityManager->flush(); return $page; } -} \ No newline at end of file +} diff --git a/app/src/Repository/MarkdownPageFilesystem.php b/app/src/Repository/MarkdownPageFilesystem.php index 1d70998..25dfd97 100644 --- a/app/src/Repository/MarkdownPageFilesystem.php +++ b/app/src/Repository/MarkdownPageFilesystem.php @@ -13,6 +13,7 @@ use function count; use function file_get_contents; use function glob; use function is_array; +use function random_int; use function str_replace; use function substr; use function usleep; @@ -31,7 +32,7 @@ final class MarkdownPageFilesystem implements MarkdownPageRepo $fileNames = glob($this->dataPath . '*.md'); assert(is_array($fileNames)); return array_map(function (string $name): MarkdownPage { - usleep(rand(200, 500) * 1000); + usleep(random_int(200, 500) * 1000); $content = file_get_contents($name); $name = str_replace($this->dataPath, '', $name); $name = str_replace('.md', '', $name); diff --git a/app/src/Template/GithubMarkdownRenderer.php b/app/src/Template/GithubMarkdownRenderer.php new file mode 100644 index 0000000..121504b --- /dev/null +++ b/app/src/Template/GithubMarkdownRenderer.php @@ -0,0 +1,28 @@ +addExtension($commonMarkCoreExtension); + $environment->addExtension($githubFlavoredMarkdownExtension); + $this->engine = new MarkdownConverter($environment); + } + + public function render(string $markdown): string + { + return (string) $this->engine->convert($markdown); + } +} diff --git a/app/src/Template/MarkdownParser.php b/app/src/Template/MarkdownParser.php new file mode 100644 index 0000000..d404005 --- /dev/null +++ b/app/src/Template/MarkdownParser.php @@ -0,0 +1,8 @@ +parser->parse($markdown); + } +} diff --git a/app/templates/page/list.html b/app/templates/page/list.html new file mode 100644 index 0000000..bf42348 --- /dev/null +++ b/app/templates/page/list.html @@ -0,0 +1,19 @@ + + + + + Pages + + + +
+ +
+ + \ No newline at end of file diff --git a/app/templates/page/show.html b/app/templates/page/show.html new file mode 100644 index 0000000..ebe707a --- /dev/null +++ b/app/templates/page/show.html @@ -0,0 +1,13 @@ + + + + + {{title}} + + + +
+ {{{content}}} +
+ + \ No newline at end of file diff --git a/implementation/02-composer/composer.lock b/implementation/02-composer/composer.lock index 31ecc9c..6d84747 100644 --- a/implementation/02-composer/composer.lock +++ b/implementation/02-composer/composer.lock @@ -4,191 +4,9 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "83df32885d5f14b3e3e8dd6d88fafd23", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "phpstan/phpstan", - "version": "1.4.10", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.4.10" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-14T10:25:45+00:00" - } - ], + "content-hash": "bad16ec07065a74b5b41da54ff6ca57e", + "packages": [], + "packages-dev": [], "aliases": [], "minimum-stability": "stable", "stability-flags": [], From 7a0f368e001fbe72845a62bf99d4b9d0e267dc71 Mon Sep 17 00:00:00 2001 From: lubiana Date: Wed, 6 Apr 2022 01:21:17 +0200 Subject: [PATCH 279/314] add chapter about data repositories, and start work on perfomance chapter --- 11-templating.md | 6 +- 12-configuration.md | 1 - 13-refactoring.md | 4 - 14-middleware.md | 9 +- 15-adding-content.md | 7 +- 16-data-repository.md | 265 + 17-performance.md | 20 + app/composer.json | 2 - app/composer.lock | 2006 +------- app/config/dependencies.php | 11 +- app/config/settings.php | 11 - app/data/pages/04-development-helpers.md | 4 +- app/data/pages/11-templating.md | 6 +- app/src/Action/Page.php | 54 +- app/src/Model/MarkdownPage.php | 8 - .../Repository/FileSystemMarkdownPageRepo.php | 61 + app/src/Repository/MarkdownPageRepo.php | 12 +- app/src/Settings.php | 17 - app/templates/page/show.html | 24 +- .../15-adding-content/.php-cs-fixer.php | 38 + .../15-adding-content/.phpcs.xml.dist | 9 + .../15-adding-content/cli-config.php | 13 + .../15-adding-content/composer.json | 56 + .../15-adding-content/composer.lock | 4246 +++++++++++++++++ .../15-adding-content/config/dependencies.php | 60 + .../15-adding-content/config/middlewares.php | 11 + .../15-adding-content/config/routes.php | 15 + .../15-adding-content/config/settings.php | 23 + .../data/pages/01-front-controller.md | 53 + .../data/pages/02-composer.md | 75 + .../data/pages/03-error-handler.md | 79 + .../data/pages/04-development-helpers.md | 260 + .../15-adding-content/data/pages/05-http.md | 124 + .../15-adding-content/data/pages/06-router.md | 101 + .../data/pages/07-dispatching-to-a-class.md | 137 + .../data/pages/08-inversion-of-control.md | 54 + .../data/pages/09-dependency-injector.md | 213 + .../data/pages/10-invoker.md | 102 + .../data/pages/11-templating.md | 240 + .../data/pages/12-configuration.md | 201 + .../data/pages/13-refactoring.md | 377 ++ .../data/pages/14-middleware.md | 298 ++ .../15-adding-content/phpstan-baseline.neon | 7 + implementation/15-adding-content/phpstan.neon | 8 + .../public/css/spectre-exp.min.css | 1 + .../public/css/spectre-icons.min.css | 1 + .../public/css/spectre.min.css | 1 + .../15-adding-content/public/favicon.ico | Bin 0 -> 15086 bytes .../15-adding-content/public/index.php | 5 + implementation/15-adding-content/src/.gitkeep | 0 .../15-adding-content/src/Action/Hello.php | 31 + .../15-adding-content/src/Action/Other.php | 16 + .../15-adding-content/src/Action/Page.php | 80 + .../15-adding-content/src/Bootstrap.php | 40 + .../src/Exception/InternalServerError.php | 9 + .../src/Exception/MethodNotAllowed.php | 9 + .../src/Exception/NotFound.php | 9 + .../src/Factory/ContainerProvider.php | 10 + .../src/Factory/DiactorosRequestFactory.php | 28 + .../src/Factory/DoctrineEm.php | 32 + .../Factory/FileSystemSettingsProvider.php | 22 + .../src/Factory/PipelineProvider.php | 25 + .../src/Factory/RequestFactory.php | 11 + .../src/Factory/SettingsContainerProvider.php | 26 + .../src/Factory/SettingsProvider.php | 10 + .../src/Http/BasicEmitter.php | 38 + .../src/Http/ContainerPipeline.php | 82 + .../15-adding-content/src/Http/Emitter.php | 10 + .../src/Http/InvokerRoutedHandler.php | 34 + .../15-adding-content/src/Http/Pipeline.php | 11 + .../src/Http/RouteMiddleware.php | 69 + .../src/Http/RoutedRequestHandler.php | 10 + .../15-adding-content/src/Kernel.php | 32 + .../src/Middleware/CacheMiddleware.php | 0 .../src/Model/MarkdownPage.php | 21 + .../src/Repository/CachedMarkdownPageRepo.php | 0 .../Repository/DoctrineMarkdownPageRepo.php | 0 .../src/Repository/MarkdownPageFilesystem.php | 0 .../src/Repository/MarkdownPageRepo.php | 19 + .../src/Service/Time/Now.php | 10 + .../src/Service/Time/SystemClockNow.php | 13 + .../15-adding-content/src/Settings.php | 33 + .../src/Template/GithubMarkdownRenderer.php | 0 .../src/Template/MarkdownParser.php | 8 + .../src/Template/MustacheRenderer.php | 17 + .../src/Template/ParsedownParser.php | 17 + .../src/Template/Renderer.php | 11 + .../15-adding-content/templates/hello.html | 6 + .../15-adding-content/templates/page.html | 5 + .../templates/page/list.html | 19 + .../templates/page/show.html | 17 + .../15-adding-content/templates/pagelist.html | 11 + .../templates/partials/foot.html | 3 + .../templates/partials/head.html | 11 + .../16-data-repository/.php-cs-fixer.php | 38 + .../16-data-repository/.phpcs.xml.dist | 9 + .../16-data-repository/cli-config.php | 13 + .../16-data-repository/composer.json | 54 + .../16-data-repository/composer.lock | 2438 ++++++++++ .../config/dependencies.php | 55 + .../16-data-repository/config/middlewares.php | 11 + .../16-data-repository/config/routes.php | 15 + .../16-data-repository/config/settings.php | 12 + .../data/pages/01-front-controller.md | 53 + .../data/pages/02-composer.md | 75 + .../data/pages/03-error-handler.md | 79 + .../data/pages/04-development-helpers.md | 260 + .../16-data-repository/data/pages/05-http.md | 124 + .../data/pages/06-router.md | 101 + .../data/pages/07-dispatching-to-a-class.md | 137 + .../data/pages/08-inversion-of-control.md | 54 + .../data/pages/09-dependency-injector.md | 213 + .../data/pages/10-invoker.md | 102 + .../data/pages/11-templating.md | 236 + .../data/pages/12-configuration.md | 201 + .../data/pages/13-refactoring.md | 377 ++ .../data/pages/14-middleware.md | 298 ++ .../16-data-repository/phpstan-baseline.neon | 7 + .../16-data-repository/phpstan.neon | 8 + .../public/css/spectre-exp.min.css | 1 + .../public/css/spectre-icons.min.css | 1 + .../public/css/spectre.min.css | 1 + .../16-data-repository/public/favicon.ico | Bin 0 -> 15086 bytes .../16-data-repository/public/index.php | 5 + .../16-data-repository/src/.gitkeep | 0 .../16-data-repository/src/Action/Hello.php | 31 + .../16-data-repository/src/Action/Other.php | 16 + .../16-data-repository/src/Action/Page.php | 60 + .../16-data-repository/src/Bootstrap.php | 40 + .../src/Exception/InternalServerError.php | 9 + .../src/Exception/MethodNotAllowed.php | 9 + .../src/Exception/NotFound.php | 9 + .../src/Factory/ContainerProvider.php | 10 + .../src/Factory/DiactorosRequestFactory.php | 28 + .../src/Factory/DoctrineEm.php | 32 + .../Factory/FileSystemSettingsProvider.php | 22 + .../src/Factory/PipelineProvider.php | 25 + .../src/Factory/RequestFactory.php | 11 + .../src/Factory/SettingsContainerProvider.php | 26 + .../src/Factory/SettingsProvider.php | 10 + .../src/Http/BasicEmitter.php | 38 + .../src/Http/ContainerPipeline.php | 82 + .../16-data-repository/src/Http/Emitter.php | 10 + .../src/Http/InvokerRoutedHandler.php | 34 + .../16-data-repository/src/Http/Pipeline.php | 11 + .../src/Http/RouteMiddleware.php | 69 + .../src/Http/RoutedRequestHandler.php | 10 + .../16-data-repository/src/Kernel.php | 32 + .../src/Model/MarkdownPage.php | 13 + .../Repository/FileSystemMarkdownPageRepo.php | 61 + .../src/Repository/MarkdownPageRepo.php | 15 + .../src/Service/Time/Now.php | 10 + .../src/Service/Time/SystemClockNow.php | 13 + .../16-data-repository/src/Settings.php | 16 + .../src/Template/MarkdownParser.php | 8 + .../src/Template/MustacheRenderer.php | 17 + .../src/Template/ParsedownParser.php | 17 + .../src/Template/Renderer.php | 11 + .../16-data-repository/templates/hello.html | 6 + .../16-data-repository/templates/page.html | 5 + .../templates/page/list.html | 19 + .../templates/page/show.html | 17 + .../templates/pagelist.html | 11 + .../templates/partials/foot.html | 3 + .../templates/partials/head.html | 11 + 165 files changed, 14028 insertions(+), 2028 deletions(-) create mode 100644 16-data-repository.md create mode 100644 17-performance.md create mode 100644 app/src/Repository/FileSystemMarkdownPageRepo.php create mode 100644 implementation/15-adding-content/.php-cs-fixer.php create mode 100644 implementation/15-adding-content/.phpcs.xml.dist create mode 100644 implementation/15-adding-content/cli-config.php create mode 100644 implementation/15-adding-content/composer.json create mode 100644 implementation/15-adding-content/composer.lock create mode 100644 implementation/15-adding-content/config/dependencies.php create mode 100644 implementation/15-adding-content/config/middlewares.php create mode 100644 implementation/15-adding-content/config/routes.php create mode 100644 implementation/15-adding-content/config/settings.php create mode 100644 implementation/15-adding-content/data/pages/01-front-controller.md create mode 100644 implementation/15-adding-content/data/pages/02-composer.md create mode 100644 implementation/15-adding-content/data/pages/03-error-handler.md create mode 100644 implementation/15-adding-content/data/pages/04-development-helpers.md create mode 100644 implementation/15-adding-content/data/pages/05-http.md create mode 100644 implementation/15-adding-content/data/pages/06-router.md create mode 100644 implementation/15-adding-content/data/pages/07-dispatching-to-a-class.md create mode 100644 implementation/15-adding-content/data/pages/08-inversion-of-control.md create mode 100644 implementation/15-adding-content/data/pages/09-dependency-injector.md create mode 100644 implementation/15-adding-content/data/pages/10-invoker.md create mode 100644 implementation/15-adding-content/data/pages/11-templating.md create mode 100644 implementation/15-adding-content/data/pages/12-configuration.md create mode 100644 implementation/15-adding-content/data/pages/13-refactoring.md create mode 100644 implementation/15-adding-content/data/pages/14-middleware.md create mode 100644 implementation/15-adding-content/phpstan-baseline.neon create mode 100644 implementation/15-adding-content/phpstan.neon create mode 100644 implementation/15-adding-content/public/css/spectre-exp.min.css create mode 100644 implementation/15-adding-content/public/css/spectre-icons.min.css create mode 100644 implementation/15-adding-content/public/css/spectre.min.css create mode 100644 implementation/15-adding-content/public/favicon.ico create mode 100644 implementation/15-adding-content/public/index.php create mode 100644 implementation/15-adding-content/src/.gitkeep create mode 100644 implementation/15-adding-content/src/Action/Hello.php create mode 100644 implementation/15-adding-content/src/Action/Other.php create mode 100644 implementation/15-adding-content/src/Action/Page.php create mode 100644 implementation/15-adding-content/src/Bootstrap.php create mode 100644 implementation/15-adding-content/src/Exception/InternalServerError.php create mode 100644 implementation/15-adding-content/src/Exception/MethodNotAllowed.php create mode 100644 implementation/15-adding-content/src/Exception/NotFound.php create mode 100644 implementation/15-adding-content/src/Factory/ContainerProvider.php create mode 100644 implementation/15-adding-content/src/Factory/DiactorosRequestFactory.php create mode 100644 implementation/15-adding-content/src/Factory/DoctrineEm.php create mode 100644 implementation/15-adding-content/src/Factory/FileSystemSettingsProvider.php create mode 100644 implementation/15-adding-content/src/Factory/PipelineProvider.php create mode 100644 implementation/15-adding-content/src/Factory/RequestFactory.php create mode 100644 implementation/15-adding-content/src/Factory/SettingsContainerProvider.php create mode 100644 implementation/15-adding-content/src/Factory/SettingsProvider.php create mode 100644 implementation/15-adding-content/src/Http/BasicEmitter.php create mode 100644 implementation/15-adding-content/src/Http/ContainerPipeline.php create mode 100644 implementation/15-adding-content/src/Http/Emitter.php create mode 100644 implementation/15-adding-content/src/Http/InvokerRoutedHandler.php create mode 100644 implementation/15-adding-content/src/Http/Pipeline.php create mode 100644 implementation/15-adding-content/src/Http/RouteMiddleware.php create mode 100644 implementation/15-adding-content/src/Http/RoutedRequestHandler.php create mode 100644 implementation/15-adding-content/src/Kernel.php rename {app => implementation/15-adding-content}/src/Middleware/CacheMiddleware.php (100%) create mode 100644 implementation/15-adding-content/src/Model/MarkdownPage.php rename {app => implementation/15-adding-content}/src/Repository/CachedMarkdownPageRepo.php (100%) rename {app => implementation/15-adding-content}/src/Repository/DoctrineMarkdownPageRepo.php (100%) rename {app => implementation/15-adding-content}/src/Repository/MarkdownPageFilesystem.php (100%) create mode 100644 implementation/15-adding-content/src/Repository/MarkdownPageRepo.php create mode 100644 implementation/15-adding-content/src/Service/Time/Now.php create mode 100644 implementation/15-adding-content/src/Service/Time/SystemClockNow.php create mode 100644 implementation/15-adding-content/src/Settings.php rename {app => implementation/15-adding-content}/src/Template/GithubMarkdownRenderer.php (100%) create mode 100644 implementation/15-adding-content/src/Template/MarkdownParser.php create mode 100644 implementation/15-adding-content/src/Template/MustacheRenderer.php create mode 100644 implementation/15-adding-content/src/Template/ParsedownParser.php create mode 100644 implementation/15-adding-content/src/Template/Renderer.php create mode 100644 implementation/15-adding-content/templates/hello.html create mode 100644 implementation/15-adding-content/templates/page.html create mode 100644 implementation/15-adding-content/templates/page/list.html create mode 100644 implementation/15-adding-content/templates/page/show.html create mode 100644 implementation/15-adding-content/templates/pagelist.html create mode 100644 implementation/15-adding-content/templates/partials/foot.html create mode 100644 implementation/15-adding-content/templates/partials/head.html create mode 100644 implementation/16-data-repository/.php-cs-fixer.php create mode 100644 implementation/16-data-repository/.phpcs.xml.dist create mode 100644 implementation/16-data-repository/cli-config.php create mode 100644 implementation/16-data-repository/composer.json create mode 100644 implementation/16-data-repository/composer.lock create mode 100644 implementation/16-data-repository/config/dependencies.php create mode 100644 implementation/16-data-repository/config/middlewares.php create mode 100644 implementation/16-data-repository/config/routes.php create mode 100644 implementation/16-data-repository/config/settings.php create mode 100644 implementation/16-data-repository/data/pages/01-front-controller.md create mode 100644 implementation/16-data-repository/data/pages/02-composer.md create mode 100644 implementation/16-data-repository/data/pages/03-error-handler.md create mode 100644 implementation/16-data-repository/data/pages/04-development-helpers.md create mode 100644 implementation/16-data-repository/data/pages/05-http.md create mode 100644 implementation/16-data-repository/data/pages/06-router.md create mode 100644 implementation/16-data-repository/data/pages/07-dispatching-to-a-class.md create mode 100644 implementation/16-data-repository/data/pages/08-inversion-of-control.md create mode 100644 implementation/16-data-repository/data/pages/09-dependency-injector.md create mode 100644 implementation/16-data-repository/data/pages/10-invoker.md create mode 100644 implementation/16-data-repository/data/pages/11-templating.md create mode 100644 implementation/16-data-repository/data/pages/12-configuration.md create mode 100644 implementation/16-data-repository/data/pages/13-refactoring.md create mode 100644 implementation/16-data-repository/data/pages/14-middleware.md create mode 100644 implementation/16-data-repository/phpstan-baseline.neon create mode 100644 implementation/16-data-repository/phpstan.neon create mode 100644 implementation/16-data-repository/public/css/spectre-exp.min.css create mode 100644 implementation/16-data-repository/public/css/spectre-icons.min.css create mode 100644 implementation/16-data-repository/public/css/spectre.min.css create mode 100644 implementation/16-data-repository/public/favicon.ico create mode 100644 implementation/16-data-repository/public/index.php create mode 100644 implementation/16-data-repository/src/.gitkeep create mode 100644 implementation/16-data-repository/src/Action/Hello.php create mode 100644 implementation/16-data-repository/src/Action/Other.php create mode 100644 implementation/16-data-repository/src/Action/Page.php create mode 100644 implementation/16-data-repository/src/Bootstrap.php create mode 100644 implementation/16-data-repository/src/Exception/InternalServerError.php create mode 100644 implementation/16-data-repository/src/Exception/MethodNotAllowed.php create mode 100644 implementation/16-data-repository/src/Exception/NotFound.php create mode 100644 implementation/16-data-repository/src/Factory/ContainerProvider.php create mode 100644 implementation/16-data-repository/src/Factory/DiactorosRequestFactory.php create mode 100644 implementation/16-data-repository/src/Factory/DoctrineEm.php create mode 100644 implementation/16-data-repository/src/Factory/FileSystemSettingsProvider.php create mode 100644 implementation/16-data-repository/src/Factory/PipelineProvider.php create mode 100644 implementation/16-data-repository/src/Factory/RequestFactory.php create mode 100644 implementation/16-data-repository/src/Factory/SettingsContainerProvider.php create mode 100644 implementation/16-data-repository/src/Factory/SettingsProvider.php create mode 100644 implementation/16-data-repository/src/Http/BasicEmitter.php create mode 100644 implementation/16-data-repository/src/Http/ContainerPipeline.php create mode 100644 implementation/16-data-repository/src/Http/Emitter.php create mode 100644 implementation/16-data-repository/src/Http/InvokerRoutedHandler.php create mode 100644 implementation/16-data-repository/src/Http/Pipeline.php create mode 100644 implementation/16-data-repository/src/Http/RouteMiddleware.php create mode 100644 implementation/16-data-repository/src/Http/RoutedRequestHandler.php create mode 100644 implementation/16-data-repository/src/Kernel.php create mode 100644 implementation/16-data-repository/src/Model/MarkdownPage.php create mode 100644 implementation/16-data-repository/src/Repository/FileSystemMarkdownPageRepo.php create mode 100644 implementation/16-data-repository/src/Repository/MarkdownPageRepo.php create mode 100644 implementation/16-data-repository/src/Service/Time/Now.php create mode 100644 implementation/16-data-repository/src/Service/Time/SystemClockNow.php create mode 100644 implementation/16-data-repository/src/Settings.php create mode 100644 implementation/16-data-repository/src/Template/MarkdownParser.php create mode 100644 implementation/16-data-repository/src/Template/MustacheRenderer.php create mode 100644 implementation/16-data-repository/src/Template/ParsedownParser.php create mode 100644 implementation/16-data-repository/src/Template/Renderer.php create mode 100644 implementation/16-data-repository/templates/hello.html create mode 100644 implementation/16-data-repository/templates/page.html create mode 100644 implementation/16-data-repository/templates/page/list.html create mode 100644 implementation/16-data-repository/templates/page/show.html create mode 100644 implementation/16-data-repository/templates/pagelist.html create mode 100644 implementation/16-data-repository/templates/partials/foot.html create mode 100644 implementation/16-data-repository/templates/partials/head.html diff --git a/11-templating.md b/11-templating.md index 3759664..7bfe1aa 100644 --- a/11-templating.md +++ b/11-templating.md @@ -49,11 +49,7 @@ namespace Lubian\NoFramework\Template; interface Renderer { - /** - * @param string $template - * @param array $data - * @return string - */ + /** @param array $data */ public function render(string $template, array $data = []) : string; } ``` diff --git a/12-configuration.md b/12-configuration.md index a44dfd5..4b60c19 100644 --- a/12-configuration.md +++ b/12-configuration.md @@ -172,7 +172,6 @@ return [ ``` Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: -require __DIR__ . '/../vendor/autoload.php'; ```php ... diff --git a/13-refactoring.md b/13-refactoring.md index 067e168..6dbbb8d 100644 --- a/13-refactoring.md +++ b/13-refactoring.md @@ -97,10 +97,6 @@ use Psr\Http\Server\RequestHandlerInterface; interface RoutedRequestHandler extends RequestHandlerInterface { - /** - * sets the Name of the ServerRequest attribute where the route - * information should be stored. - */ public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void; } ``` diff --git a/14-middleware.md b/14-middleware.md index 087e26b..81f82a5 100644 --- a/14-middleware.md +++ b/14-middleware.md @@ -153,8 +153,6 @@ class ContainerPipeline implements Pipeline { /** * @param array $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container */ public function __construct( private array $middlewares, @@ -295,4 +293,11 @@ Lets try if you can make the kernel work with our created Pipeline implementatio pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a dependency container, if you want you can do that as well. +**A quick note about docblocks:** You might have noticed, that I rarely add docblocks to my the code in the examples, and +when I do it seems kind of random. My philosophy is that I only add docblocks when there is no way to automatically get +the exact type from the code itself. For me docblocks only serve two purposes: help my IDE to understand what it choices +it has for code completion and to help the static analysis to better understand the code. There is a great blogpost +about the [cost and value of DocBlocks](https://localheinz.com/blog/2018/05/06/cost-and-value-of-docblocks/), although it +is written in 2018 at a time before PHP 7.4 was around everything written there is still valid today. + [<< previous](12-refactoring.md) | [next >>](15-adding-content.md) diff --git a/15-adding-content.md b/15-adding-content.md index d1a7348..c894d66 100644 --- a/15-adding-content.md +++ b/15-adding-content.md @@ -114,6 +114,10 @@ those are simply displayed using an unordered list. {{title}} + + +
@@ -129,7 +133,8 @@ html and thereby destroys the the parsed markdown. You might have spotted that I added [Pico.css](https://picocss.com/) which is just a very small css framework to make the pages a little bit nicer to look at. It mostly provides some typography styles that work great with rendered Markdown, -but you can leave that out or use any other css framework you like. +but you can leave that out or use any other css framework you like. There is also some Javascript that adds syntax +highlighting to the code. After you have taken care of the templating side we can now create an new Action class with two methods to display use our markdown files and the templates to create the pages. As we have two templates I propose to use Two methods in our diff --git a/16-data-repository.md b/16-data-repository.md new file mode 100644 index 0000000..d9a3218 --- /dev/null +++ b/16-data-repository.md @@ -0,0 +1,265 @@ +[<< previous](15-adding-content.md) | [next >>](17-performance.md) + +## Data Repository + +At the end of the last chapter I mentioned being unhappy with our Pages action, because there is to much stuff happening +there. We are firstly receiving some Arguments, then we are using those to query the filesytem for the given page, +loading the specific file from the filesystem, rendering the markdown, passing the markdown to the template renderer, +adding the resulting html to the response and then returning the response. + +In order to make our pageaction independent from the filesystem and move the code that is responsible for reading the +files +to a better place I want to introduce +the [Repository Pattern](https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html). + +I want to start by creating a class that represents the Data that is included in a page so that. For now I can spot +three +distrinct attributes. + +* the ID (or chapternumber) +* the title (or name) +* the content + +Currently all those properties are always available, but we might later be able to create new pages and store them, but +at that point in time we are not yet aware of the new available ID, so we should leave that property nullable. This +allows +us to create an object without an id and let the code that actually saves the object to a persistant store define a +valid +id on saving. + +Lets create an new Namespace called `Model` and put a `MarkdownPage.php` class in there: + +```php +dataPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return array_map(function (string $filename) { + $content = file_get_contents($filename); + if ($content === false) { + throw new InternalServerError('cannot read pages'); + } + $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); + return new MarkdownPage( + (int) substr($idAndTitle, 0, 2), + substr($idAndTitle, 3), + $content + ); + }, $files); + } + + public function byName(string $name): MarkdownPage + { + $pages = array_values( + array_filter( + $this->all(), + fn (MarkdownPage $p) => $p->title === $name, + ) + ); + + if (count($pages) !== 1) { + throw new NotFound; + } + + return $pages[0]; + } +} +``` + +With that in place we need to add the required `$pagesPath` to our settings class and add specify that in our +configuration. + +`src/Settings.php` + +```php +final class Settings +{ + public function __construct( + public readonly string $environment, + public readonly string $dependenciesFile, + public readonly string $middlewaresFile, + public readonly string $templateDir, + public readonly string $templateExtension, + public readonly string $pagesPath, + ) { + } +} +``` + +`config/settings.php` + +```php +return new Settings( + environment: 'prod', + dependenciesFile: __DIR__ . '/dependencies.php', + middlewaresFile: __DIR__ . '/middlewares.php', + templateDir: __DIR__ . '/../templates', + templateExtension: '.html', + pagesPath: __DIR__ . '/../data/pages/', +); +``` + +Of course we need to define the correct implementation for the container to choose when we are requesting the Repository +interface: +`conf/dependencies.php` + +```php +MarkdownPageRepo::class => fn (FileSystemMarkdownPageRepo $r) => $r, +FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), +``` + +Now you can request the MarkdownPageRepo Interface in your page action and use the defined functions to get the +MarkdownPage +Objects. My `src/Action/Page.php` looks like this now: + +```php +repo->byName($page); + + // fix the next and previous buttons to work with our routing + $content = preg_replace('/\(\d\d-/m', '(', $page->content); + assert(is_string($content)); + $content = str_replace('.md)', ')', $content); + + $data = [ + 'title' => $page->title, + 'content' => $this->parser->parse($content), + ]; + + $html = $this->renderer->render('page/show', $data); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map(function (MarkdownPage $page) { + return [ + 'id' => $page->id, + 'title' => $page->content, + ]; + }, $this->repo->all()); + + $html = $this->renderer->render('page/list', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } +} +``` + +Check the page in your browser if everything still works, don't forget to run phpstan and the others fixers before +committing your changes and moving on to the next chapter. + +[<< previous](15-adding-content.md) | [next >>](17-performance.md) diff --git a/17-performance.md b/17-performance.md new file mode 100644 index 0000000..d457bff --- /dev/null +++ b/17-performance.md @@ -0,0 +1,20 @@ +[<< previous](15-adding-content.md) | [next >>](17-performance.md) + +## Performance + +Although our application is still very small and you should not really experience any performance issues right now, +there are still some things we can already consider and take a look at. If I check the network tab in my browser it takes +about 90-400ms to show a simple rendered markdownpage, with is sort of ok but in my opinion way to long as we are not +really doing anything and do not connect to any external services. Mostly we are just reading around 16 markdown files, +a template, some config files here and there and parse some markdown. So that should not really take that long. + +The problem is, that we heavily rely on autoloading for all our class files, in the `src` folder. And there are also +quite a lot of other files in composers `vendor` directory. To understand while this is becomming we should make +ourselves familiar with how autoloading in PHP works. + +[autoloading in php](https://www.php.net/manual/en/language.oop5.autoload.php) +[composer autoloader optimization](https://getcomposer.org/doc/articles/autoloader-optimization.md) + +### Composer autoloading + +[<< previous](15-adding-content.md) | [next >>](17-performance.md) diff --git a/app/composer.json b/app/composer.json index 4809539..b5c7f1a 100644 --- a/app/composer.json +++ b/app/composer.json @@ -12,8 +12,6 @@ "middlewares/trailing-slash": "^2.0", "middlewares/whoops": "^2.0", "erusev/parsedown": "^1.7", - "symfony/cache": "^6.0", - "doctrine/orm": "^2.11", "league/commonmark": "^2.2" }, "autoload": { diff --git a/app/composer.lock b/app/composer.lock index 648f2d5..a62d9c7 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1e8469bfebe6479a139b946b8aba49de", + "content-hash": "00acf07ae222f9117a84bce157b99837", "packages": [ { "name": "dflydev/dot-access-data", @@ -81,935 +81,6 @@ }, "time": "2021-08-13T13:06:58+00:00" }, - { - "name": "doctrine/cache", - "version": "2.1.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/cache.git", - "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/331b4d5dbaeab3827976273e9356b3b453c300ce", - "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce", - "shasum": "" - }, - "require": { - "php": "~7.1 || ^8.0" - }, - "conflict": { - "doctrine/common": ">2.2,<2.4" - }, - "require-dev": { - "alcaeus/mongo-php-adapter": "^1.1", - "cache/integration-tests": "dev-master", - "doctrine/coding-standard": "^8.0", - "mongodb/mongodb": "^1.1", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "predis/predis": "~1.0", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^4.4 || ^5.2 || ^6.0@dev", - "symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev" - }, - "suggest": { - "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", - "homepage": "https://www.doctrine-project.org/projects/cache.html", - "keywords": [ - "abstraction", - "apcu", - "cache", - "caching", - "couchdb", - "memcached", - "php", - "redis", - "xcache" - ], - "support": { - "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/2.1.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", - "type": "tidelift" - } - ], - "time": "2021-07-17T14:49:29+00:00" - }, - { - "name": "doctrine/collections", - "version": "1.6.8", - "source": { - "type": "git", - "url": "https://github.com/doctrine/collections.git", - "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/1958a744696c6bb3bb0d28db2611dc11610e78af", - "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af", - "shasum": "" - }, - "require": { - "php": "^7.1.3 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5", - "vimeo/psalm": "^4.2.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", - "homepage": "https://www.doctrine-project.org/projects/collections.html", - "keywords": [ - "array", - "collections", - "iterators", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/1.6.8" - }, - "time": "2021-08-10T18:51:53+00:00" - }, - { - "name": "doctrine/common", - "version": "3.2.2", - "source": { - "type": "git", - "url": "https://github.com/doctrine/common.git", - "reference": "295082d3750987065912816a9d536c2df735f637" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/295082d3750987065912816a9d536c2df735f637", - "reference": "295082d3750987065912816a9d536c2df735f637", - "shasum": "" - }, - "require": { - "doctrine/persistence": "^2.0", - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "^1.4.1", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", - "squizlabs/php_codesniffer": "^3.0", - "symfony/phpunit-bridge": "^4.0.5", - "vimeo/psalm": "^4.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", - "homepage": "https://www.doctrine-project.org/projects/common.html", - "keywords": [ - "common", - "doctrine", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/common/issues", - "source": "https://github.com/doctrine/common/tree/3.2.2" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", - "type": "tidelift" - } - ], - "time": "2022-02-02T09:15:57+00:00" - }, - { - "name": "doctrine/dbal", - "version": "3.3.4", - "source": { - "type": "git", - "url": "https://github.com/doctrine/dbal.git", - "reference": "83f779beaea1893c0bece093ab2104c6d15a7f26" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/83f779beaea1893c0bece093ab2104c6d15a7f26", - "reference": "83f779beaea1893c0bece093ab2104c6d15a7f26", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2", - "doctrine/cache": "^1.11|^2.0", - "doctrine/deprecations": "^0.5.3", - "doctrine/event-manager": "^1.0", - "php": "^7.3 || ^8.0", - "psr/cache": "^1|^2|^3", - "psr/log": "^1|^2|^3" - }, - "require-dev": { - "doctrine/coding-standard": "9.0.0", - "jetbrains/phpstorm-stubs": "2021.1", - "phpstan/phpstan": "1.4.6", - "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "9.5.16", - "psalm/plugin-phpunit": "0.16.1", - "squizlabs/php_codesniffer": "3.6.2", - "symfony/cache": "^5.2|^6.0", - "symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0", - "vimeo/psalm": "4.22.0" - }, - "suggest": { - "symfony/console": "For helpful console commands such as SQL execution and import of files." - }, - "bin": [ - "bin/doctrine-dbal" - ], - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\DBAL\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - } - ], - "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", - "homepage": "https://www.doctrine-project.org/projects/dbal.html", - "keywords": [ - "abstraction", - "database", - "db2", - "dbal", - "mariadb", - "mssql", - "mysql", - "oci8", - "oracle", - "pdo", - "pgsql", - "postgresql", - "queryobject", - "sasql", - "sql", - "sqlite", - "sqlserver", - "sqlsrv" - ], - "support": { - "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.3.4" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", - "type": "tidelift" - } - ], - "time": "2022-03-20T18:37:29+00:00" - }, - { - "name": "doctrine/deprecations", - "version": "v0.5.3", - "source": { - "type": "git", - "url": "https://github.com/doctrine/deprecations.git", - "reference": "9504165960a1f83cc1480e2be1dd0a0478561314" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314", - "reference": "9504165960a1f83cc1480e2be1dd0a0478561314", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^6.0|^7.0|^8.0", - "phpunit/phpunit": "^7.0|^8.0|^9.0", - "psr/log": "^1.0" - }, - "suggest": { - "psr/log": "Allows logging deprecations via PSR-3 logger implementation" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", - "homepage": "https://www.doctrine-project.org/", - "support": { - "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v0.5.3" - }, - "time": "2021-03-21T12:59:47+00:00" - }, - { - "name": "doctrine/event-manager", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/event-manager.git", - "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", - "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/common": "<2.9@dev" - }, - "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpunit/phpunit": "^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/event-manager.html", - "keywords": [ - "event", - "event dispatcher", - "event manager", - "event system", - "events" - ], - "support": { - "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/1.1.x" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", - "type": "tidelift" - } - ], - "time": "2020-05-29T18:28:51+00:00" - }, - { - "name": "doctrine/inflector", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/doctrine/inflector.git", - "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", - "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^8.2", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "vimeo/psalm": "^4.10" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", - "homepage": "https://www.doctrine-project.org/projects/inflector.html", - "keywords": [ - "inflection", - "inflector", - "lowercase", - "manipulation", - "php", - "plural", - "singular", - "strings", - "uppercase", - "words" - ], - "support": { - "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.4" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", - "type": "tidelift" - } - ], - "time": "2021-10-22T20:16:43+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2022-03-03T08:28:38+00:00" - }, - { - "name": "doctrine/lexer", - "version": "1.2.3", - "source": { - "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.11" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", - "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.2.3" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" - } - ], - "time": "2022-02-28T11:07:21+00:00" - }, - { - "name": "doctrine/orm", - "version": "2.11.2", - "source": { - "type": "git", - "url": "https://github.com/doctrine/orm.git", - "reference": "9c351e044478135aec1755e2c0c0493a4b6309db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/9c351e044478135aec1755e2c0c0493a4b6309db", - "reference": "9c351e044478135aec1755e2c0c0493a4b6309db", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2", - "doctrine/cache": "^1.12.1 || ^2.1.1", - "doctrine/collections": "^1.5", - "doctrine/common": "^3.0.3", - "doctrine/dbal": "^2.13.1 || ^3.2", - "doctrine/deprecations": "^0.5.3", - "doctrine/event-manager": "^1.1", - "doctrine/inflector": "^1.4 || ^2.0", - "doctrine/instantiator": "^1.3", - "doctrine/lexer": "^1.0", - "doctrine/persistence": "^2.2", - "ext-ctype": "*", - "php": "^7.1 || ^8.0", - "psr/cache": "^1 || ^2 || ^3", - "symfony/console": "^3.0 || ^4.0 || ^5.0 || ^6.0", - "symfony/polyfill-php72": "^1.23", - "symfony/polyfill-php80": "^1.15" - }, - "conflict": { - "doctrine/annotations": "<1.13 || >= 2.0" - }, - "require-dev": { - "doctrine/annotations": "^1.13", - "doctrine/coding-standard": "^9.0", - "phpbench/phpbench": "^0.16.10 || ^1.0", - "phpstan/phpstan": "1.4.6", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", - "squizlabs/php_codesniffer": "3.6.2", - "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0", - "vimeo/psalm": "4.22.0" - }, - "suggest": { - "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0", - "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" - }, - "bin": [ - "bin/doctrine" - ], - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\ORM\\": "lib/Doctrine/ORM" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "Object-Relational-Mapper for PHP", - "homepage": "https://www.doctrine-project.org/projects/orm.html", - "keywords": [ - "database", - "orm" - ], - "support": { - "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/2.11.2" - }, - "time": "2022-03-09T15:23:58+00:00" - }, - { - "name": "doctrine/persistence", - "version": "2.4.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/persistence.git", - "reference": "092a52b71410ac1795287bb5135704ef07d18dd0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/092a52b71410ac1795287bb5135704ef07d18dd0", - "reference": "092a52b71410ac1795287bb5135704ef07d18dd0", - "shasum": "" - }, - "require": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/collections": "^1.0", - "doctrine/deprecations": "^0.5.3", - "doctrine/event-manager": "^1.0", - "php": "^7.1 || ^8.0", - "psr/cache": "^1.0 || ^2.0 || ^3.0" - }, - "conflict": { - "doctrine/annotations": "<1.0 || >=2.0", - "doctrine/common": "<2.10" - }, - "require-dev": { - "composer/package-versions-deprecated": "^1.11", - "doctrine/annotations": "^1.0", - "doctrine/coding-standard": "^9.0", - "doctrine/common": "^3.0", - "phpstan/phpstan": "1.4.6", - "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.5", - "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "vimeo/psalm": "4.21.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "src/Common", - "Doctrine\\Persistence\\": "src/Persistence" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", - "homepage": "https://doctrine-project.org/projects/persistence.html", - "keywords": [ - "mapper", - "object", - "odm", - "orm", - "persistence" - ], - "support": { - "issues": "https://github.com/doctrine/persistence/issues", - "source": "https://github.com/doctrine/persistence/tree/2.4.1" - }, - "time": "2022-03-22T06:44:40+00:00" - }, { "name": "erusev/parsedown", "version": "1.7.4", @@ -2074,55 +1145,6 @@ }, "time": "2020-10-12T12:39:22+00:00" }, - { - "name": "psr/cache", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" - }, - "time": "2021-02-03T23:26:27+00:00" - }, { "name": "psr/container", "version": "1.1.2", @@ -2494,118 +1516,21 @@ "time": "2021-07-14T16:46:02+00:00" }, { - "name": "symfony/cache", - "version": "v6.0.6", + "name": "symfony/deprecation-contracts", + "version": "v3.0.1", "source": { "type": "git", - "url": "https://github.com/symfony/cache.git", - "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", - "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", "shasum": "" }, "require": { - "php": ">=8.0.2", - "psr/cache": "^2.0|^3.0", - "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^1.1.7|^2|^3", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/var-exporter": "^5.4|^6.0" - }, - "conflict": { - "doctrine/dbal": "<2.13.1", - "symfony/dependency-injection": "<5.4", - "symfony/http-kernel": "<5.4", - "symfony/var-dumper": "<5.4" - }, - "provide": { - "psr/cache-implementation": "2.0|3.0", - "psr/simple-cache-implementation": "1.0|2.0|3.0", - "symfony/cache-implementation": "1.1|2.0|3.0" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/dbal": "^2.13.1|^3.0", - "predis/predis": "^1.1", - "psr/simple-cache": "^1.0|^2.0|^3.0", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/filesystem": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/messenger": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Cache\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", - "homepage": "https://symfony.com", - "keywords": [ - "caching", - "psr6" - ], - "support": { - "source": "https://github.com/symfony/cache/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "symfony/cache-contracts", - "version": "v3.0.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache-contracts.git", - "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/2f7463f156cf9c665d9317e21a809c3bbff5754e", - "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "psr/cache": "^3.0" - }, - "suggest": { - "symfony/cache-implementation": "" + "php": ">=8.0.2" }, "type": "library", "extra": { @@ -2617,176 +1542,6 @@ "url": "https://github.com/symfony/contracts" } }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Cache\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to caching", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.0.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-08-17T15:35:52+00:00" - }, - { - "name": "symfony/console", - "version": "v6.0.5", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "3bebf4108b9e07492a2a4057d207aa5a77d146b1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/3bebf4108b9e07492a2a4057d207aa5a77d146b1", - "reference": "3bebf4108b9e07492a2a4057d207aa5a77d146b1", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.4|^6.0" - }, - "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/lock": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command line", - "console", - "terminal" - ], - "support": { - "source": "https://github.com/symfony/console/tree/v6.0.5" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-02-25T10:48:52+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v2.5.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8", - "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, "autoload": { "files": [ "function.php" @@ -2809,7 +1564,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.1" }, "funding": [ { @@ -2825,413 +1580,7 @@ "type": "tidelift" } ], - "time": "2021-07-12T14:48:14+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "30885182c981ab175d4d034db0f6f469898070ab" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", - "reference": "30885182c981ab175d4d034db0f6f469898070ab", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-10-20T20:35:02+00:00" - }, - { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "81b86b50cf841a64252b439e738e97f4a34e2783" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", - "reference": "81b86b50cf841a64252b439e738e97f4a34e2783", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-23T21:10:46+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-02-19T12:13:01+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/polyfill-php72", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", - "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-05-27T09:17:38+00:00" + "time": "2022-01-02T09:55:41+00:00" }, { "name": "symfony/polyfill-php80", @@ -3315,246 +1664,6 @@ } ], "time": "2022-03-04T08:16:47+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v2.5.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", - "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "suggest": { - "symfony/service-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-04T16:48:04+00:00" - }, - { - "name": "symfony/string", - "version": "v6.0.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/522144f0c4c004c80d56fa47e40e17028e2eefc2", - "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/translation-contracts": "<2.0" - }, - "require-dev": { - "symfony/error-handler": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/translation-contracts": "^2.0|^3.0", - "symfony/var-exporter": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "files": [ - "Resources/functions.php" - ], - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "source": "https://github.com/symfony/string/tree/v6.0.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:55:41+00:00" - }, - { - "name": "symfony/var-exporter", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-exporter.git", - "reference": "130229a482abf17635a685590958894dfb4b4360" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/130229a482abf17635a685590958894dfb4b4360", - "reference": "130229a482abf17635a685590958894dfb4b4360", - "shasum": "" - }, - "require": { - "php": ">=8.0.2" - }, - "require-dev": { - "symfony/var-dumper": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\VarExporter\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows exporting any serializable PHP data structure to plain PHP code", - "homepage": "https://symfony.com", - "keywords": [ - "clone", - "construct", - "export", - "hydrate", - "instantiate", - "serialize" - ], - "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" } ], "packages-dev": [ @@ -3864,16 +1973,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.5.3", + "version": "1.5.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7" + "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/39953ac1452a8843702ee41a35b4861d3e8207a7", - "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", + "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", "shasum": "" }, "require": { @@ -3899,7 +2008,7 @@ "description": "PHPStan - PHP Static Analysis Tool", "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.3" + "source": "https://github.com/phpstan/phpstan/tree/1.5.4" }, "funding": [ { @@ -3919,7 +2028,7 @@ "type": "tidelift" } ], - "time": "2022-03-30T21:55:08+00:00" + "time": "2022-04-03T12:39:00+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -4089,6 +2198,89 @@ }, "time": "2021-12-12T21:44:58+00:00" }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, { "name": "symfony/var-dumper", "version": "v6.0.6", diff --git a/app/config/dependencies.php b/app/config/dependencies.php index e2a3925..0040933 100644 --- a/app/config/dependencies.php +++ b/app/config/dependencies.php @@ -1,10 +1,8 @@ fn (InvokerRoutedHandler $h) => $h, RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, CacheInterface::class => fn (FilesystemAdapter $a) => $a, - MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, MarkdownParser::class => fn (ParsedownParser $p) => $p, + MarkdownPageRepo::class => fn (FileSystemMarkdownPageRepo $r) => $r, // Factories ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), @@ -54,7 +51,5 @@ return [ ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - MarkdownPageFilesystem::class => fn (Settings $s) => new MarkdownPageFilesystem($s->pagesPath), - CachedMarkdownPageRepo::class => fn (CacheInterface $c, MarkdownPageFilesystem $r, Settings $s) => new CachedMarkdownPageRepo($c, $r, $s), - EntityManagerInterface::class => fn (DoctrineEm $f) => $f->create(), + FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), ]; diff --git a/app/config/settings.php b/app/config/settings.php index 5c58216..c654565 100644 --- a/app/config/settings.php +++ b/app/config/settings.php @@ -9,15 +9,4 @@ return new Settings( templateDir: __DIR__ . '/../templates', templateExtension: '.html', pagesPath: __DIR__ . '/../data/pages/', - connection: [ - 'driver' => 'pdo_sqlite', - 'user' => '', - 'password' => '', - 'path' => __DIR__ . '/../data/db.sqlite', - ], - doctrine: [ - 'devMode' => true, - 'metadataDirs' => [__DIR__ . '/../src/Model/'], - 'cacheDir' => __DIR__ . '/../data/cache/', - ], ); diff --git a/app/data/pages/04-development-helpers.md b/app/data/pages/04-development-helpers.md index 74f913c..9505284 100644 --- a/app/data/pages/04-development-helpers.md +++ b/app/data/pages/04-development-helpers.md @@ -174,7 +174,7 @@ return $config The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. -it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. +it provides the `phpcs` command to check for violations and the `phpcbf` command to actually fix most of the violations. Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have guessed we are adding some extra rules. @@ -216,7 +216,7 @@ PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY Time: 639ms; Memory: 10MB ``` -You can then use `./vendor/bin/phpcbf` to try to fix them +You can then use `./vendor/bin/phpcbf` to try to fix them. #### Symfony Var-Dumper diff --git a/app/data/pages/11-templating.md b/app/data/pages/11-templating.md index 3759664..7bfe1aa 100644 --- a/app/data/pages/11-templating.md +++ b/app/data/pages/11-templating.md @@ -49,11 +49,7 @@ namespace Lubian\NoFramework\Template; interface Renderer { - /** - * @param string $template - * @param array $data - * @return string - */ + /** @param array $data */ public function render(string $template, array $data = []) : string; } ``` diff --git a/app/src/Action/Page.php b/app/src/Action/Page.php index 6a3aad0..4af45f0 100644 --- a/app/src/Action/Page.php +++ b/app/src/Action/Page.php @@ -2,20 +2,17 @@ namespace Lubian\NoFramework\Action; -use Lubian\NoFramework\Exception\InternalServerError; +use Lubian\NoFramework\Model\MarkdownPage; +use Lubian\NoFramework\Repository\MarkdownPageRepo; use Lubian\NoFramework\Template\MarkdownParser; use Lubian\NoFramework\Template\Renderer; use Psr\Http\Message\ResponseInterface; -use function array_filter; use function array_map; -use function array_values; -use function file_get_contents; -use function glob; +use function assert; +use function is_string; use function preg_replace; -use function str_contains; use function str_replace; -use function substr; class Page { @@ -23,30 +20,25 @@ class Page private ResponseInterface $response, private MarkdownParser $parser, private Renderer $renderer, - private string $pagesPath = __DIR__ . '/../../data/pages/' + private MarkdownPageRepo $repo, ) { } public function show( string $page, ): ResponseInterface { - $page = array_values( - array_filter( - $this->getPages(), - fn (string $filename) => str_contains($filename, $page) - ) - )[0]; - $markdown = file_get_contents($page); + $page = $this->repo->byName($page); // fix the next and previous buttons to work with our routing - $markdown = preg_replace('/\(\d\d-/m', '(', $markdown); - $markdown = str_replace('.md)', ')', $markdown); + $content = preg_replace('/\(\d\d-/m', '(', $page->content); + assert(is_string($content)); + $content = str_replace('.md)', ')', $content); - $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); $data = [ - 'title' => substr($page, 3), - 'content' => $this->parser->parse($markdown), + 'title' => $page->title, + 'content' => $this->parser->parse($content), ]; + $html = $this->renderer->render('page/show', $data); $this->response->getBody()->write($html); return $this->response; @@ -54,27 +46,15 @@ class Page public function list(): ResponseInterface { - $pages = array_map(function (string $page) { - $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + $pages = array_map(function (MarkdownPage $page) { return [ - 'id' => substr($page, 0, 2), - 'title' => substr($page, 3), + 'id' => $page->id, + 'title' => $page->content, ]; - }, $this->getPages()); + }, $this->repo->all()); + $html = $this->renderer->render('page/list', ['pages' => $pages]); $this->response->getBody()->write($html); return $this->response; } - - /** - * @return string[] - */ - private function getPages(): array - { - $files = glob($this->pagesPath . '*.md'); - if ($files === false) { - throw new InternalServerError('cannot read pages'); - } - return $files; - } } diff --git a/app/src/Model/MarkdownPage.php b/app/src/Model/MarkdownPage.php index bae383c..df244fd 100644 --- a/app/src/Model/MarkdownPage.php +++ b/app/src/Model/MarkdownPage.php @@ -2,19 +2,11 @@ namespace Lubian\NoFramework\Model; -use Doctrine\DBAL\Types\Types; - -#[Entity] class MarkdownPage { public function __construct( - #[Id, - Column, - GeneratedValue] public int |null $id = null, - #[Column] public string $title, - #[Column(type: Types::TEXT)] public string $content, ) { } diff --git a/app/src/Repository/FileSystemMarkdownPageRepo.php b/app/src/Repository/FileSystemMarkdownPageRepo.php new file mode 100644 index 0000000..cca350e --- /dev/null +++ b/app/src/Repository/FileSystemMarkdownPageRepo.php @@ -0,0 +1,61 @@ +dataPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return array_map(function (string $filename) { + $content = file_get_contents($filename); + if ($content === false) { + throw new InternalServerError('cannot read pages'); + } + $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); + return new MarkdownPage( + (int) substr($idAndTitle, 0, 2), + substr($idAndTitle, 3), + $content + ); + }, $files); + } + + public function byName(string $name): MarkdownPage + { + $pages = array_values( + array_filter( + $this->all(), + fn (MarkdownPage $p) => $p->title === $name, + ) + ); + + if (count($pages) !== 1) { + throw new NotFound; + } + + return $pages[0]; + } +} diff --git a/app/src/Repository/MarkdownPageRepo.php b/app/src/Repository/MarkdownPageRepo.php index 3f80899..0792d32 100644 --- a/app/src/Repository/MarkdownPageRepo.php +++ b/app/src/Repository/MarkdownPageRepo.php @@ -2,18 +2,14 @@ namespace Lubian\NoFramework\Repository; +use Lubian\NoFramework\Exception\NotFound; use Lubian\NoFramework\Model\MarkdownPage; interface MarkdownPageRepo { - /** - * @return MarkdownPage[] - */ + /** @return MarkdownPage[] */ public function all(): array; - public function byId(int $id): MarkdownPage; - - public function byTitle(string $title): MarkdownPage; - - public function save(MarkdownPage $page): MarkdownPage; + /** @throws NotFound */ + public function byName(string $name): MarkdownPage; } diff --git a/app/src/Settings.php b/app/src/Settings.php index a6e4218..885aa7b 100644 --- a/app/src/Settings.php +++ b/app/src/Settings.php @@ -4,10 +4,6 @@ namespace Lubian\NoFramework; final class Settings { - /** - * @param array{driver: string, user: string, password: string, path: string} $connection - * @param array{devMode: bool, metadataDirs: string[], cacheDir: string} $doctrine - */ public function __construct( public readonly string $environment, public readonly string $dependenciesFile, @@ -15,19 +11,6 @@ final class Settings public readonly string $templateDir, public readonly string $templateExtension, public readonly string $pagesPath, - /** - * @var array{driver: string, user: string, password: string, path: string} - */ - public readonly array $connection, - /** - * @var array{devMode: bool, metadataDirs: string[], cacheDir: string} - */ - public readonly array $doctrine, ) { } - - public function isDev(): bool - { - return $this->environment === 'dev'; - } } diff --git a/app/templates/page/show.html b/app/templates/page/show.html index ebe707a..abe295e 100644 --- a/app/templates/page/show.html +++ b/app/templates/page/show.html @@ -1,13 +1,17 @@ - - - {{title}} - - - -
- {{{content}}} -
- + + + {{title}} + + + + + + +
+ {{{content}}} +
+ \ No newline at end of file diff --git a/implementation/15-adding-content/.php-cs-fixer.php b/implementation/15-adding-content/.php-cs-fixer.php new file mode 100644 index 0000000..705a7d7 --- /dev/null +++ b/implementation/15-adding-content/.php-cs-fixer.php @@ -0,0 +1,38 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config' + ]) + ); \ No newline at end of file diff --git a/implementation/15-adding-content/.phpcs.xml.dist b/implementation/15-adding-content/.phpcs.xml.dist new file mode 100644 index 0000000..3b433f6 --- /dev/null +++ b/implementation/15-adding-content/.phpcs.xml.dist @@ -0,0 +1,9 @@ + + + + + src + config + + + \ No newline at end of file diff --git a/implementation/15-adding-content/cli-config.php b/implementation/15-adding-content/cli-config.php new file mode 100644 index 0000000..fbc6598 --- /dev/null +++ b/implementation/15-adding-content/cli-config.php @@ -0,0 +1,13 @@ +getContainer(); + +return ConsoleRunner::createHelperSet($container->get(EntityManagerInterface::class)); diff --git a/implementation/15-adding-content/composer.json b/implementation/15-adding-content/composer.json new file mode 100644 index 0000000..4809539 --- /dev/null +++ b/implementation/15-adding-content/composer.json @@ -0,0 +1,56 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3", + "mustache/mustache": "^2.14", + "psr/http-server-middleware": "^1.0", + "middlewares/trailing-slash": "^2.0", + "middlewares/whoops": "^2.0", + "erusev/parsedown": "^1.7", + "symfony/cache": "^6.0", + "doctrine/orm": "^2.11", + "league/commonmark": "^2.2" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require-dev": { + "phpstan/phpstan": "^1.5", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0", + "squizlabs/php_codesniffer": "^3.6", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.1", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "mnapoli/hard-mode": "^0.3.0" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1234 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + } +} diff --git a/implementation/15-adding-content/composer.lock b/implementation/15-adding-content/composer.lock new file mode 100644 index 0000000..648f2d5 --- /dev/null +++ b/implementation/15-adding-content/composer.lock @@ -0,0 +1,4246 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "1e8469bfebe6479a139b946b8aba49de", + "packages": [ + { + "name": "dflydev/dot-access-data", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^3.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" + }, + "time": "2021-08-13T13:06:58+00:00" + }, + { + "name": "doctrine/cache", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/331b4d5dbaeab3827976273e9356b3b453c300ce", + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce", + "shasum": "" + }, + "require": { + "php": "~7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "alcaeus/mongo-php-adapter": "^1.1", + "cache/integration-tests": "dev-master", + "doctrine/coding-standard": "^8.0", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "predis/predis": "~1.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.2 || ^6.0@dev", + "symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", + "keywords": [ + "abstraction", + "apcu", + "cache", + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" + ], + "support": { + "issues": "https://github.com/doctrine/cache/issues", + "source": "https://github.com/doctrine/cache/tree/2.1.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "type": "tidelift" + } + ], + "time": "2021-07-17T14:49:29+00:00" + }, + { + "name": "doctrine/collections", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/1958a744696c6bb3bb0d28db2611dc11610e78af", + "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af", + "shasum": "" + }, + "require": { + "php": "^7.1.3 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5", + "vimeo/psalm": "^4.2.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", + "keywords": [ + "array", + "collections", + "iterators", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/collections/issues", + "source": "https://github.com/doctrine/collections/tree/1.6.8" + }, + "time": "2021-08-10T18:51:53+00:00" + }, + { + "name": "doctrine/common", + "version": "3.2.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "295082d3750987065912816a9d536c2df735f637" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/295082d3750987065912816a9d536c2df735f637", + "reference": "295082d3750987065912816a9d536c2df735f637", + "shasum": "" + }, + "require": { + "doctrine/persistence": "^2.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^1.4.1", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.0", + "symfony/phpunit-bridge": "^4.0.5", + "vimeo/psalm": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", + "homepage": "https://www.doctrine-project.org/projects/common.html", + "keywords": [ + "common", + "doctrine", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/common/issues", + "source": "https://github.com/doctrine/common/tree/3.2.2" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", + "type": "tidelift" + } + ], + "time": "2022-02-02T09:15:57+00:00" + }, + { + "name": "doctrine/dbal", + "version": "3.3.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "83f779beaea1893c0bece093ab2104c6d15a7f26" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/83f779beaea1893c0bece093ab2104c6d15a7f26", + "reference": "83f779beaea1893c0bece093ab2104c6d15a7f26", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/cache": "^1.11|^2.0", + "doctrine/deprecations": "^0.5.3", + "doctrine/event-manager": "^1.0", + "php": "^7.3 || ^8.0", + "psr/cache": "^1|^2|^3", + "psr/log": "^1|^2|^3" + }, + "require-dev": { + "doctrine/coding-standard": "9.0.0", + "jetbrains/phpstorm-stubs": "2021.1", + "phpstan/phpstan": "1.4.6", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "9.5.16", + "psalm/plugin-phpunit": "0.16.1", + "squizlabs/php_codesniffer": "3.6.2", + "symfony/cache": "^5.2|^6.0", + "symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0", + "vimeo/psalm": "4.22.0" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\DBAL\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", + "homepage": "https://www.doctrine-project.org/projects/dbal.html", + "keywords": [ + "abstraction", + "database", + "db2", + "dbal", + "mariadb", + "mssql", + "mysql", + "oci8", + "oracle", + "pdo", + "pgsql", + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlite", + "sqlserver", + "sqlsrv" + ], + "support": { + "issues": "https://github.com/doctrine/dbal/issues", + "source": "https://github.com/doctrine/dbal/tree/3.3.4" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", + "type": "tidelift" + } + ], + "time": "2022-03-20T18:37:29+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "v0.5.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "9504165960a1f83cc1480e2be1dd0a0478561314" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314", + "reference": "9504165960a1f83cc1480e2be1dd0a0478561314", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0|^7.0|^8.0", + "phpunit/phpunit": "^7.0|^8.0|^9.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v0.5.3" + }, + "time": "2021-03-21T12:59:47+00:00" + }, + { + "name": "doctrine/event-manager", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": "<2.9@dev" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/1.1.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2020-05-29T18:28:51+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "vimeo/psalm": "^4.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2021-10-22T20:16:43+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-03-03T08:28:38+00:00" + }, + { + "name": "doctrine/lexer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2022-02-28T11:07:21+00:00" + }, + { + "name": "doctrine/orm", + "version": "2.11.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/orm.git", + "reference": "9c351e044478135aec1755e2c0c0493a4b6309db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/orm/zipball/9c351e044478135aec1755e2c0c0493a4b6309db", + "reference": "9c351e044478135aec1755e2c0c0493a4b6309db", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/cache": "^1.12.1 || ^2.1.1", + "doctrine/collections": "^1.5", + "doctrine/common": "^3.0.3", + "doctrine/dbal": "^2.13.1 || ^3.2", + "doctrine/deprecations": "^0.5.3", + "doctrine/event-manager": "^1.1", + "doctrine/inflector": "^1.4 || ^2.0", + "doctrine/instantiator": "^1.3", + "doctrine/lexer": "^1.0", + "doctrine/persistence": "^2.2", + "ext-ctype": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3", + "symfony/console": "^3.0 || ^4.0 || ^5.0 || ^6.0", + "symfony/polyfill-php72": "^1.23", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "doctrine/annotations": "<1.13 || >= 2.0" + }, + "require-dev": { + "doctrine/annotations": "^1.13", + "doctrine/coding-standard": "^9.0", + "phpbench/phpbench": "^0.16.10 || ^1.0", + "phpstan/phpstan": "1.4.6", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "squizlabs/php_codesniffer": "3.6.2", + "symfony/cache": "^4.4 || ^5.4 || ^6.0", + "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0", + "vimeo/psalm": "4.22.0" + }, + "suggest": { + "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0", + "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" + }, + "bin": [ + "bin/doctrine" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\ORM\\": "lib/Doctrine/ORM" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "Object-Relational-Mapper for PHP", + "homepage": "https://www.doctrine-project.org/projects/orm.html", + "keywords": [ + "database", + "orm" + ], + "support": { + "issues": "https://github.com/doctrine/orm/issues", + "source": "https://github.com/doctrine/orm/tree/2.11.2" + }, + "time": "2022-03-09T15:23:58+00:00" + }, + { + "name": "doctrine/persistence", + "version": "2.4.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/persistence.git", + "reference": "092a52b71410ac1795287bb5135704ef07d18dd0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/092a52b71410ac1795287bb5135704ef07d18dd0", + "reference": "092a52b71410ac1795287bb5135704ef07d18dd0", + "shasum": "" + }, + "require": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/collections": "^1.0", + "doctrine/deprecations": "^0.5.3", + "doctrine/event-manager": "^1.0", + "php": "^7.1 || ^8.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0" + }, + "conflict": { + "doctrine/annotations": "<1.0 || >=2.0", + "doctrine/common": "<2.10" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.11", + "doctrine/annotations": "^1.0", + "doctrine/coding-standard": "^9.0", + "doctrine/common": "^3.0", + "phpstan/phpstan": "1.4.6", + "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6.0", + "vimeo/psalm": "4.21.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "src/Common", + "Doctrine\\Persistence\\": "src/Persistence" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", + "homepage": "https://doctrine-project.org/projects/persistence.html", + "keywords": [ + "mapper", + "object", + "odm", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/persistence/issues", + "source": "https://github.com/doctrine/persistence/tree/2.4.1" + }, + "time": "2022-03-22T06:44:40+00:00" + }, + { + "name": "erusev/parsedown", + "version": "1.7.4", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "support": { + "issues": "https://github.com/erusev/parsedown/issues", + "source": "https://github.com/erusev/parsedown/tree/1.7.x" + }, + "time": "2019-12-30T22:54:17+00:00" + }, + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-03-29T20:12:16+00:00" + }, + { + "name": "league/commonmark", + "version": "2.2.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", + "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.15" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.30.0", + "commonmark/commonmark.js": "0.30.0", + "composer/package-versions-deprecated": "^1.8", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4", + "phpstan/phpstan": "^0.12.88 || ^1.0.0", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2022-02-26T21:24:45+00:00" + }, + { + "name": "league/config", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.90", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2021-08-14T12:15:32+00:00" + }, + { + "name": "middlewares/trailing-slash", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/middlewares/trailing-slash.git", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", + "shasum": "" + }, + "require": { + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to normalize the trailing slash of the uri path", + "homepage": "https://github.com/middlewares/trailing-slash", + "keywords": [ + "http", + "middleware", + "normalize", + "path", + "psr-15", + "psr-7", + "slash" + ], + "support": { + "issues": "https://github.com/middlewares/trailing-slash/issues", + "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" + }, + "time": "2020-12-02T00:06:55+00:00" + }, + { + "name": "middlewares/utils", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/middlewares/utils.git", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v2.16", + "guzzlehttp/psr7": "^2.0", + "laminas/laminas-diactoros": "^2.4", + "nyholm/psr7": "^1.0", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "slim/psr7": "^1.4", + "squizlabs/php_codesniffer": "^3.5", + "sunrise/http-message": "^1.0", + "sunrise/http-server-request": "^1.0", + "sunrise/stream": "^1.0.15", + "sunrise/uri": "^1.0.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\Utils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Common utils for PSR-15 middleware packages", + "homepage": "https://github.com/middlewares/utils", + "keywords": [ + "PSR-11", + "http", + "middleware", + "psr-15", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/middlewares/utils/issues", + "source": "https://github.com/middlewares/utils/tree/v3.3.0" + }, + "time": "2021-07-04T17:56:23+00:00" + }, + { + "name": "middlewares/whoops", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/middlewares/whoops.git", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.5", + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^5.0 || ^7.0", + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to use Whoops as error handler", + "homepage": "https://github.com/middlewares/whoops", + "keywords": [ + "error", + "http", + "middleware", + "psr-15", + "psr-7", + "server", + "whoops" + ], + "support": { + "issues": "https://github.com/middlewares/whoops/issues", + "source": "https://github.com/middlewares/whoops/tree/v2.0.2" + }, + "time": "2022-01-27T20:31:30+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nette/schema", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", + "shasum": "" + }, + "require": { + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": ">=7.1 <8.2" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "phpstan/phpstan-nette": "^0.12", + "tracy/tracy": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.2.2" + }, + "time": "2021-10-15T11:40:02+00:00" + }, + { + "name": "nette/utils", + "version": "v3.2.7", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", + "shasum": "" + }, + "require": { + "php": ">=7.2 <8.2" + }, + "conflict": { + "nette/di": "<3.0.6" + }, + "require-dev": { + "nette/tester": "~2.0", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v3.2.7" + }, + "time": "2022-01-24T11:29:14+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/master" + }, + "time": "2018-10-30T17:12:04+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "symfony/cache", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", + "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^1.1.7|^2|^3", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/var-exporter": "^5.4|^6.0" + }, + "conflict": { + "doctrine/dbal": "<2.13.1", + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/var-dumper": "<5.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^2.13.1|^3.0", + "predis/predis": "^1.1", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/2f7463f156cf9c665d9317e21a809c3bbff5754e", + "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "psr/cache": "^3.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.0.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-08-17T15:35:52+00:00" + }, + { + "name": "symfony/console", + "version": "v6.0.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "3bebf4108b9e07492a2a4057d207aa5a77d146b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/3bebf4108b9e07492a2a4057d207aa5a77d146b1", + "reference": "3bebf4108b9e07492a2a4057d207aa5a77d146b1", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.4|^6.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/lock": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.0.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-02-25T10:48:52+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-07-12T14:48:14+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "30885182c981ab175d4d034db0f6f469898070ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", + "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-10-20T20:35:02+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "81b86b50cf841a64252b439e738e97f4a34e2783" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", + "reference": "81b86b50cf841a64252b439e738e97f4a34e2783", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-23T21:10:46+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T09:17:38+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-04T08:16:47+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-04T16:48:04+00:00" + }, + { + "name": "symfony/string", + "version": "v6.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/522144f0c4c004c80d56fa47e40e17028e2eefc2", + "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.0" + }, + "require-dev": { + "symfony/error-handler": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/translation-contracts": "^2.0|^3.0", + "symfony/var-exporter": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v6.0.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:55:41+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "130229a482abf17635a685590958894dfb4b4360" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/130229a482abf17635a685590958894dfb4b4360", + "reference": "130229a482abf17635a685590958894dfb4b4360", + "shasum": "" + }, + "require": { + "php": ">=8.0.2" + }, + "require-dev": { + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "doctrine/coding-standard", + "version": "8.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/coding-standard.git", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Müller", + "email": "st.mueller@dzh-online.de" + } + ], + "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "doctrine", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/doctrine/coding-standard/issues", + "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" + }, + "time": "2021-04-03T10:54:55+00:00" + }, + { + "name": "mnapoli/hard-mode", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/hard-mode.git", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", + "shasum": "" + }, + "require": { + "doctrine/coding-standard": "^8.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Hard mode for PHP", + "support": { + "issues": "https://github.com/mnapoli/hard-mode/issues", + "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" + }, + "time": "2020-10-12T07:54:37+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.5.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/39953ac1452a8843702ee41a35b4861d3e8207a7", + "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.5.3" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-03-30T21:55:08+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^1.2.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" + }, + "time": "2021-11-18T09:30:29+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/15-adding-content/config/dependencies.php b/implementation/15-adding-content/config/dependencies.php new file mode 100644 index 0000000..e2a3925 --- /dev/null +++ b/implementation/15-adding-content/config/dependencies.php @@ -0,0 +1,60 @@ + fn (SystemClockNow $n) => $n, + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, + Emitter::class => fn (BasicEmitter $e) => $e, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, + CacheInterface::class => fn (FilesystemAdapter $a) => $a, + MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, + MarkdownParser::class => fn (ParsedownParser $p) => $p, + + // Factories + ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), + Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + MarkdownPageFilesystem::class => fn (Settings $s) => new MarkdownPageFilesystem($s->pagesPath), + CachedMarkdownPageRepo::class => fn (CacheInterface $c, MarkdownPageFilesystem $r, Settings $s) => new CachedMarkdownPageRepo($c, $r, $s), + EntityManagerInterface::class => fn (DoctrineEm $f) => $f->create(), +]; diff --git a/implementation/15-adding-content/config/middlewares.php b/implementation/15-adding-content/config/middlewares.php new file mode 100644 index 0000000..71dd461 --- /dev/null +++ b/implementation/15-adding-content/config/middlewares.php @@ -0,0 +1,11 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/page', [Page::class, 'list']); + $r->addRoute('GET', '/page/{page}', [Page::class, 'show']); + $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); + $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/implementation/15-adding-content/config/settings.php b/implementation/15-adding-content/config/settings.php new file mode 100644 index 0000000..5c58216 --- /dev/null +++ b/implementation/15-adding-content/config/settings.php @@ -0,0 +1,23 @@ + 'pdo_sqlite', + 'user' => '', + 'password' => '', + 'path' => __DIR__ . '/../data/db.sqlite', + ], + doctrine: [ + 'devMode' => true, + 'metadataDirs' => [__DIR__ . '/../src/Model/'], + 'cacheDir' => __DIR__ . '/../data/cache/', + ], +); diff --git a/implementation/15-adding-content/data/pages/01-front-controller.md b/implementation/15-adding-content/data/pages/01-front-controller.md new file mode 100644 index 0000000..87a12ad --- /dev/null +++ b/implementation/15-adding-content/data/pages/01-front-controller.md @@ -0,0 +1,53 @@ +[next >>](02-composer.md) + +### Front Controller + +A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. + +To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. + +A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. + +The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. + +But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. + +So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. + +Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: + +```php +>](02-composer.md) diff --git a/implementation/15-adding-content/data/pages/02-composer.md b/implementation/15-adding-content/data/pages/02-composer.md new file mode 100644 index 0000000..a25a4a8 --- /dev/null +++ b/implementation/15-adding-content/data/pages/02-composer.md @@ -0,0 +1,75 @@ +[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) + +### Composer + +[Composer](https://getcomposer.org/) is a dependency manager for PHP. + +Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do +something. With Composer, you can install third-party libraries for your application. + +If you don't have Composer installed already, head over to the website and install it. You can find Composer packages +for your project on [Packagist](https://packagist.org/). + +Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will +be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. + +Add the following content to the file: + +```json +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubiana", + "email": "lubiana@hannover.ccc.de" + } + ] +} +``` + +In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use +whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. +Just replace it with your namespace in your own code. + +I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. + +As the Bootstrap.php file is placed in that directory we should +add the namespace to the File as well. Here is my current Bootstrap.php +as a reference: + +```php +>](03-error-handler.md) diff --git a/implementation/15-adding-content/data/pages/03-error-handler.md b/implementation/15-adding-content/data/pages/03-error-handler.md new file mode 100644 index 0000000..60465d0 --- /dev/null +++ b/implementation/15-adding-content/data/pages/03-error-handler.md @@ -0,0 +1,79 @@ +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) + +### Error Handler + +An error handler allows you to customize what happens if your code results in an error. + +A nice error page with a lot of information for debugging goes a long way during development. So the first package +for your application will take care of that. + +I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. +If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, +you have total control over your project. + +An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) + +To install a new package, open up your `composer.json` and add the package to the require part. It should now look +like this: + +```php +"require": { + "php": ">=8.1.0", + "filp/whoops": "^2.14" +}, +``` + +Now run `composer update` in your console and it will be installed. + +Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, +i that case composer automatically installs the package and updates your composer.json-file. + +But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, +ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you +only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. + +**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message +can help someone to gain access to your system. Always show a user friendly error page instead and send an email to +yourself, write to a log or something similar. So only you can see the errors in the production environment. + +For development that does not make sense though -- you want a nice error page. The solution is to have an environment +switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in +case no environment has been set. + +Then after the error handler registration, throw an `Exception` to test if everything is working correctly. +Your `Bootstrap.php` should now look similar to this: + +```php +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (\Throwable $e) { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +throw new \Exception("Ooooopsie"); + +``` + +You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until +you get it working. Now would also be a good time for another commit. + + +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/implementation/15-adding-content/data/pages/04-development-helpers.md b/implementation/15-adding-content/data/pages/04-development-helpers.md new file mode 100644 index 0000000..74f913c --- /dev/null +++ b/implementation/15-adding-content/data/pages/04-development-helpers.md @@ -0,0 +1,260 @@ +[<< previous](03-error-handler.md) | [next >>](05-http.md) + +### Development Helpers + +I have added some more helpers to my composer.json that help me with development. As these are scripts and programms +used only for development they should not be used in a production environment. Composer has a specific sections in its +file called "dev-dependencies", everything that is required in this section does not get installen in production. + +Let's install our dev-helpers and i will explain them one by one: +`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` + +#### Static Code Analysis with phpstan + +Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or +create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements +that are not (or not always) available, if-statements that always are true and a lot of other stuff. + +A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. + +```php +/** + * @param \DateTime $date + * @return void + */ +function printDate($date) { + $date->format('Y-m-d H:i:s'); +} + +printDate('now'); +``` +if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` + +It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has +no use, and secondly, that we are calling the function with a string instead of a datetime object. + +```shell +1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + ------ --------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ --------------------------------------------------------------------------------------------- +30 Call to method DateTime::format() on a separate line has no effect. +33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. + ------ --------------------------------------------------------------------------------------------- +``` + +The second error is something that "declare strict-types" already catches for us, but the first error is something that +we usually would not discover easily without speccially looking for this errortype. + +We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and +path everytime we want to check our code for errors: + +```yaml +parameters: + level: max + paths: + - src +``` +now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project + +With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us +some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. + +```shell +composer require --dev phpstan/extension-installer +composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules +``` + +During the first install you need to allow the extension installer to actually install the extension. The second command +installs some more strict rulesets and activates them in phpstan. + +If we now rerun phpstan it already tells us about some errors we have made: + +``` + ------ ----------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ ----------------------------------------------------------------------------------------------- +10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider + using long ternary. +25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: + http://bit.ly/subtypeexception +26 Unreachable statement - code above always terminates. + ------ ----------------------------------------------------------------------------------------------- +``` + +The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove +that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific +problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working +on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages +everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we +are adding to our code. + +In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the +phpstan.neon file and run phpstan with the +'--generate-baseline' option: + +```yaml +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` +```shell +[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline +Note: Using configuration file /home/vagrant/app/phpstan.neon. + 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + + + [OK] Baseline generated with 1 error. + + +``` + +you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) + +#### PHP-CS-Fixer + +Another great tool is the php-cs-fixer, which just applies a specific style to your code. + +when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current +directory. + +You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) + +personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup +a configuration file that i use in all my projects .php-cs-fixer.php: + +```php +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + ]) + ); +``` + +#### PHP Codesniffer + +The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra +rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. + +it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. + +Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have +guessed we are adding some extra rules. + +Lets install the ruleset with composer +```shell +composer require --dev mnapoli/hard-mode +``` + +and add a configuration file to actually use it '.phpcs.xml.dist' +```xml + + + + + src + + + +``` + +running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. + +``` +[vagrant@archlinux app]$ ./vendor/bin/phpcs + +FILE: src/Bootstrap.php +---------------------------------------------------------------------------------------------------- +FOUND 4 ERRORS AFFECTING 4 LINES +---------------------------------------------------------------------------------------------------- + 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. + 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead +---------------------------------------------------------------------------------------------------- +PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY +---------------------------------------------------------------------------------------------------- + +Time: 639ms; Memory: 10MB +``` + +You can then use `./vendor/bin/phpcbf` to try to fix them + + +#### Symfony Var-Dumper + +another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small +functions. + +dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects +or arrays. + +dd() on the other hand is a function that dumps its parameters and then exits the php-script. + +you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. + +#### Composer scripts + +now we have a few commands that are available on the command line. i personally do not like to type complex commands +with lots of parameters by hand all the time, so i added a few lines to my composer.json: + +```json +"scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" +}, +``` + +that way i can just type "composer" followed by the command name in the root of my project. if i want to start the +php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the +time. + +You could also configure PhpStorm to automatically run these commands in the background and highlight the violations +directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my +flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. + +My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before +commiting and pushing the code. + +[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/implementation/15-adding-content/data/pages/05-http.md b/implementation/15-adding-content/data/pages/05-http.md new file mode 100644 index 0000000..6166214 --- /dev/null +++ b/implementation/15-adding-content/data/pages/05-http.md @@ -0,0 +1,124 @@ +[<< previous](04-development-helpers.md) | [next >>](06-router.md) + +### HTTP + +PHP already has a few things built in to make working with HTTP easier. For example there are the +[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. + +These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, +if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, +then you will want a class with a nice object-oriented interface that you can use in your application instead. + +Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The +standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php +projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. + +As this is a widely adopted standard there are already several implementations available for us to use. I will choose +the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. + +Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a +[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. + +Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use +that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding +the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). + + +to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit +enter + +Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): + +```php +$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); +$response = new \Laminas\Diactoros\Response; +$response->getBody()->write('Hello World! '); +$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); +``` + +This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. + +In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the +write()-Method on that object. + + +To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: + +```php +echo $response->getBody(); +``` + +This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only +stores data. + +You can play around with the other methods of the Request object and take a look at its content with the dd() function. + +```php +dd($response) +``` + +Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot +be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which +creates a copy of the request object with the changed property and returns that clone: + +```php +$response = $response->withStatus(200); +$response = $response->withAddedHeader('Content-type', 'application/json'); +``` + +If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been +defined this way. + +But if you have been keeping attention you might argue that the following line should not work if the request object is +immutable. + +```php +$response->getBody()->write('Hello World!'); +``` + +The response-body implements a stream interface which is immutable for some reasons that are described in the +[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be +aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in +the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. +I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content +to a response object. + +```php +$body = $response->getBody(); +$body->write('Hello World!'); +$response = $response->withBody($body); +``` + +Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our +output-logic a little bit more. Replace the line that echos the response-body with the following: + +```php +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); +``` + +This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a +webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. + +Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. + +Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter + + +[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/implementation/15-adding-content/data/pages/06-router.md b/implementation/15-adding-content/data/pages/06-router.md new file mode 100644 index 0000000..6c39ae5 --- /dev/null +++ b/implementation/15-adding-content/data/pages/06-router.md @@ -0,0 +1,101 @@ +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) + +### Router + +A router dispatches to different handlers depending on rules that you have set up. + +With your current setup it does not matter what URL is used to access the application, it will always result in the same +response. So let's fix that now. + +I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own +favorite package. + +Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) + +By now you know how to install Composer packages, so I will leave that to you. + +Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. + +```php +$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +switch ($routeInfo[0]) { + case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: + $response = (new \Laminas\Diactoros\Response)->withStatus(405); + $response->getBody()->write('Method not allowed'); + $response = $response->withStatus(405); + break; + case \FastRoute\Dispatcher::FOUND: + $handler = $routeInfo[1]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + /** @var \Psr\Http\Message\ResponseInterface $response */ + $response = call_user_func($handler, $request); + break; + case \FastRoute\Dispatcher::NOT_FOUND: + default: + $response = (new \Laminas\Diactoros\Response)->withStatus(404); + $response->getBody()->write('Not Found!'); + break; +} +``` + +In the first part of the code, you are registering the available routes for your application. In the second part, the +dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, +we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. +If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. + +This setup might work for really small applications, but once you start adding a few routes your bootstrap file will +quickly get cluttered. So let's move them out into a separate file. + +Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; + +```php +addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}; +``` + +Now let's rewrite the route dispatcher part to use the `Routes.php` file. + +```php +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); +``` + +This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. + +Of course we now need to add the 'config' folder to the configuration files of our +devhelpers so that they can scan that directory as well. + +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/implementation/15-adding-content/data/pages/07-dispatching-to-a-class.md b/implementation/15-adding-content/data/pages/07-dispatching-to-a-class.md new file mode 100644 index 0000000..0c961a4 --- /dev/null +++ b/implementation/15-adding-content/data/pages/07-dispatching-to-a-class.md @@ -0,0 +1,137 @@ +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) + +### Dispatching to a Class + +In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). +MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to +learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) +and the followup posts. + +So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). + +We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other +common names are 'Controllers' or 'Actions'. + +Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. +In there, create a `Hello.php` file. + +```php +getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + } +} +``` + +You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) +that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is +fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement +it in some other parts of our application as well. In order to use that Interface we have to require it with composer: +'composer require psr/http-server-handler'. + +The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. +At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. + +Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: + +```php +return function(\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); + $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); +}; +``` + +Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared +the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing +the response to the one we defined for the second route. + +To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: + +```php +case \FastRoute\Dispatcher::FOUND: + $handler = new $routeInfo[1]; + if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { + throw new \Exception('Invalid Requesthandler'); + } + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + assert($response instanceof \Psr\Http\Message\ResponseInterface) + break; +``` + +So instead of just calling a method you are now instantiating an object and then calling the method on it. + +Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. + +And of course don't forget to commit your changes. + +Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still +generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for +example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still +have our whoopsie error-handler but i like to be more explicit in my control flow. + +In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new +Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and +InternalServerError. All three should extend phps Base Exception class. + +Here is my NotFound.php for example. + +```php + $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody()->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} +``` + +Check if our code still works, try to trigger some errors, run phpstan and the fix command +and don't forget to commit your changes. + +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/implementation/15-adding-content/data/pages/08-inversion-of-control.md b/implementation/15-adding-content/data/pages/08-inversion-of-control.md new file mode 100644 index 0000000..21f4f23 --- /dev/null +++ b/implementation/15-adding-content/data/pages/08-inversion-of-control.md @@ -0,0 +1,54 @@ +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) + +### Inversion of Control + +In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we +want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then +we would need to edit every one of our request handlers to call a different constructor of the class. + +The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that +instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done +with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). + +If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is +implemented, it will make sense. + +Change your `Hello` action to the following: + +```php +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: + +```php +$handler = new $className($response); +``` + +Of course we need to also update all the other handlers. + +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/implementation/15-adding-content/data/pages/09-dependency-injector.md b/implementation/15-adding-content/data/pages/09-dependency-injector.md new file mode 100644 index 0000000..7f7c6a2 --- /dev/null +++ b/implementation/15-adding-content/data/pages/09-dependency-injector.md @@ -0,0 +1,213 @@ +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) + +### Dependency Injector + +A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when +the class is instantiated. + +Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work +with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look +for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). + +I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) +out of the box. + +After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: + +```php +addDefinitions([ + \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), +]); + +return $builder->build(); +``` + +In this file we create a containerbuilder, add some definitions to it and return the container. +As the container supports autowiring we only need to define services where we want to use a specific implementation of +an interface. + +In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to +tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second +exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. + +Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), +as we will use that extensively. + +Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally +to create our Handler-Object + +```php +$container = require __DIR__ . '/../config/dependencies.php'; +assert($container instanceof \Psr\Container\ContainerInterface); + +$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); +assert($request instanceof \Psr\Http\Message\ServerRequestInterface); +``` + +The other part that has to be changed is the dispatching of the route. Before you had the following code: + +```php +$className = $routeInfo[1]; +$handler = new $className($response); +assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Change that to the following: + +```php +/** @var RequestHandlerInterface $handler */ +$className = $routeInfo[1]; +$handler = $container->get($className); +assert($handler instanceof RequestHandlerInterface); +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Make sure to use the container fetch the response object in the catch blocks as well: + +```php +} catch (MethodNotAllowed) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(404); + $response->getBody()->write('Not Found'); +} +``` + +Now all your controller constructor dependencies will be automatically resolved with PHP-DI. + +We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons +in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call +`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we +want to work with a different date in a test. + +Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation +that creates us a DateTimeImmutable object with the current date and time. + +src/Service/Time/Now.php: +```php +namespace Lubian\NoFramework\Service\Time; + +interface Now +{ + public function __invoke(): \DateTimeImmutable; +} +``` +src/Service/Time/SystemClockNow.php: +```php +namespace Lubian\NoFramework\Service\Time; + +final class SystemClockNow implements Now +{ + + public function __invoke(): \DateTimeImmutable + { + return new \DateTimeImmutable; + } +} +``` +If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and +update the handle-method to use the new class property: + +```php +getAttribute('name', 'Stranger'); + $nowAsString = ($this->now)()->format('H:i:s'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowAsString); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI +automatically figures out what classes are requested in the constructor and tries to create the objects needed. + +But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred +S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: + +```php + public function __construct( + private ResponseInterface $response, + private Now $now, + ) +``` + +When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation +should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: + +```php +\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), +``` + +we could also use the PHP-DI create method to delegate the object creation to the container implementation: +```php +\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), +``` + +this way the container can try to resolve any dependencies that the class might have internally, but prefer the other +method because we are not depending on this specific dependency injection implementation. + +Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are +requesting the Hello action. + +If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As +we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way +we wont get any warnings for this particular issue, but for any other issues we add to our code. + +Update the phpstan.neon file to include a "baseline" file: + +``` +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` + +if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and +ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just +'baseline' + +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/implementation/15-adding-content/data/pages/10-invoker.md b/implementation/15-adding-content/data/pages/10-invoker.md new file mode 100644 index 0000000..3033fae --- /dev/null +++ b/implementation/15-adding-content/data/pages/10-invoker.md @@ -0,0 +1,102 @@ +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) + +### Invoker + +Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the +one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long +with some services and not the whole Requestobject with all its various properties. + +If we take our Hello action for example we only need a response object, the time service and the 'name' information from +the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named +the class hello and it would be redundant to also call the the method hello. So an updated version of that class could +look like this: + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + string $name = 'Stranger', + ): ResponseInterface + { + $body = $this->response->getBody(); + $nowString = $now->get()->format('H:i:s'); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowString); + return $response + ->withBody($body) + ->withStatus(200); + } +} +``` + +It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short +closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the +rootpath of our application yet. + +```php +$r->addRoute('GET', '/hello[/{name}]', Hello::class); +$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); +$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +``` + +In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the +route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that +class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what +arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router +if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple +callable we would need to manually use reflection as well to resolve all the arguments. + +But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) +which handles all of that for us. + +After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo +switch section in the Bootstrap.php file to use the container->call() method: + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2]; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$response = $container->call($handler, $args); +``` + +Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. + +But by now you should know that I do not like to depend on specific implementations and the call method is not defined in +the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container +or any other implementation. + +Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific +container implementation so we could use it with any container that implements the ContainerInterface. And best of all +the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that +we could implement if we ever want to write our own implementation or we could write an adapter that uses a different +class that solves the same problem. + +But for now we are using the solution provided by PHP-DI. +So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2] ?? []; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$invoker = $container->get(InvokerInterface::class); +assert($invoker instanceof InvokerInterface); +$response = $invoker->call($handler, $args); +assert($response instanceof ResponseInterface); +``` + +Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) +by php, and even some more. + +But let us move on to something more fun and add some templating functionality to our application as we are trying to build +a website in the end. + +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/implementation/15-adding-content/data/pages/11-templating.md b/implementation/15-adding-content/data/pages/11-templating.md new file mode 100644 index 0000000..3759664 --- /dev/null +++ b/implementation/15-adding-content/data/pages/11-templating.md @@ -0,0 +1,240 @@ +[<< previous](10-invoker.md) | [next >>](12-configuration.md) + +### Templating + +A template engine is not necessary with PHP because the language itself can take care of that. But it can make things +like escaping values easier. They also make it easier to draw a clear line between your application logic and the +template files which should only put your variables into the HTML code. + +A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please +also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. +Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. + +For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install +that package before you continue (`composer require mustache/mustache`). + +Another well known alternative would be [Twig](http://twig.sensiolabs.org/). + +Now please go and have a look at the source code of the +[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class +does not implement an interface. + +You could just type hint against the concrete class. But the problem with this approach is that you create tight +coupling. + +In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the +implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want +to add functionality to the engine. You can't do that without going back and changing all your code that is tightly +coupled. + +What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need +another implementation, you just implement that interface in your new class and inject the new class instead. + +Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). +This sounds a lot more complicated than it is, so just follow along. + +First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). +This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. +A class can implement multiple interfaces if necessary. + +So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a +new folder in your `src/` folder with the name `Template` where you can put all the template related things. + +In there create a new interface `Renderer.php` that looks like this: + +```php + $data + * @return string + */ + public function render(string $template, array $data = []) : string; +} +``` + +Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file +`MustacheRenderer.php` with the following content: + +```php +engine->render($template, $data); + } +} +``` + +As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple +and only fulfills the interface. + +Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know +which implementation he has to inject when you hint for the interface. Add this line: + +```php +[ + ... + \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) + ->constructor(new Mustache_Engine), +] +``` + +Now update the Hello.php class to require an implementation of our renderer interface +and use that to render a string using mustache syntax. + + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + Renderer $renderer, + string $name = 'Stranger', + ): ResponseInterface { + $body = $response->getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render( + 'Hello {{name}}, the time is {{now}}!', + $data, + ); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} +``` + +Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. +But what we want is template files, so let's go back and change that. + +To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the +`dependencies.php` file and add the following code: + +```php +[ + ... + Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), + Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), +] +``` + +We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. +Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have +to rename all the template files. + +To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the +dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader +in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object +we can then use it in the factory. + +In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: + +``` +

Hello World

+Hello {{ name }} +``` + +Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` + +Navigate to the hello page in your browser to make sure everything works. + +One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies +file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration +values. + +Lets create a 'Settings' class in our './src' Folder: + +```php +addDefinitions([ + Settings::class => fn () => require __DIR__ '/settings.php', + ResponseInterface::class => create(Response::class), + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + Renderer::class => fn (ME $me) => new Mustache($me), + MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), +]); + +return $builder->build(); +``` + + + +And as always, don't forget to commit your changes. + + +[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/implementation/15-adding-content/data/pages/12-configuration.md b/implementation/15-adding-content/data/pages/12-configuration.md new file mode 100644 index 0000000..a44dfd5 --- /dev/null +++ b/implementation/15-adding-content/data/pages/12-configuration.md @@ -0,0 +1,201 @@ +[<< previous](11-templating.md) | [next >>](13-refactoring.md) + +### Configuration + +In the last chapter we added some more definitions to our dependencies.php in that definitions +we needed to pass quite a few configuration settings and filesystem strings to the constructors +of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. + +As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. + +As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. + +Lets create a 'Settings' class in our './src' Folder: + +```php +filePath; + } +} +``` + +If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files +and craft a settings object from them. + +As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another +factory for our Container because everyone should have a Friend :) + +```php +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn () => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} +``` + +For this to work we need to change our dependencies.php file to just return the array of definitions: +And here we can instantly use the Settings object to create our template engine. + +```php + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), +]; +``` + +Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: +require __DIR__ . '/../vendor/autoload.php'; + +```php +... +error_reporting(E_ALL); + +$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); +$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); +... +``` + +Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. + +[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/implementation/15-adding-content/data/pages/13-refactoring.md b/implementation/15-adding-content/data/pages/13-refactoring.md new file mode 100644 index 0000000..067e168 --- /dev/null +++ b/implementation/15-adding-content/data/pages/13-refactoring.md @@ -0,0 +1,377 @@ +[<< previous](12-configuration.md) | [next >>](14-middleware.md) + +### Refactoring + +By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no +reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. +After all the bootstrap file should just set up the classes needed for the handling logic and execute them. + +At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. +As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the +method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non +static and extracted an interface. + +'./src/Http/Emitter.php' +```php +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + echo $response->getBody(); + } +} +``` +After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following +code in the Bootstrap.php to emit the response: + +```php +/** @var Emitter $emitter */ +$emitter = $container->get(Emitter::class); +$emitter->emit($response); +``` + +If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily +write an adapter that implements your emitter interface and wraps that more advanced emitter + +Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and +calling the routerhandler that in the passes the request to a function and gets the response. + +For this to steps to be seperated we are going to create two more classes: +1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object +2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the + requestobject, fetches the correct object from the container and calls it to create a response. + +Lets create the HandlerInterface first: + +```php +getAttribute($this->routeAttributeName, false); + assert($handler !== false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} + +``` + +We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. +The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler +as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in +the next chapter. + +```php +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} +``` + +Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. +```php +[ + '...', + Emitter::class => fn (BasicEmitter $e) => $e, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, +], +``` + +And then we can update our Bootstrap.php to fetch all the services and let them handle the request. + +```php +... +$routeMiddleWare = $container->get(MiddlewareInterface::class); +assert($routeMiddleWare instanceof MiddlewareInterface); +$handler = $container->get(RoutedRequestHandler::class); +assert($handler instanceof RequestHandlerInterface); +$emitter = $container->get(Emitter::class); +assert($emitter instanceof Emitter); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$response = $routeMiddleWare->process($request, $handler); +$emitter->emit($response); +``` +Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of +code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). + +So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. + +I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class +should require to function properly. + +* A RequestFactory + We want our Kernel to be able to build the request itself +* An Emitter + Without an Emitter we will not be able to send the response to the client +* RouteMiddleware + To decore the request with the correct handler for the requested route +* RequestHandler + To delegate the request to the correct funtion that creates the response + +As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface +to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our +interface: + +```php +factory::fromGlobals(); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} +``` + +For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: + +```php +routeMiddleware->process($request, $this->handler); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} + +``` + +We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines + +```php +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); +``` + +You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking +at the Whoops output and adding the needed definitions to the dependencies.php file. + +And as always, don't forget to commit your changes. + +[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/implementation/15-adding-content/data/pages/14-middleware.md b/implementation/15-adding-content/data/pages/14-middleware.md new file mode 100644 index 0000000..e698327 --- /dev/null +++ b/implementation/15-adding-content/data/pages/14-middleware.md @@ -0,0 +1,298 @@ +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) + +### Middleware + +In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain +a bit more about what this interface does and why it is used in many applications. + +The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets +passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all +the middlewars to the client/emitter. + +So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the +response after it gets created by our handlers. + +So lets take a look at the middleware and the requesthandler interfaces + +```php +interface MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; +} + +interface RequestHandlerInterface +{ + public function handle(ServerRequestInterface $request): ResponseInterface; +} +``` + +The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a +requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the +response. + +But the middleware could just ignore the handler and produce a response on its own as the interface just requires us +to produce a response. + +A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users +that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the +database. + +In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates +the request with an 'isAuthenticated' attribute. + +If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that +response is not already cached, than we let the handler create the response and store that in the cache for a few +seconds + +```php +interface CacheInterface +{ + public function get(string $key, callable $resolver, int $ttl): mixed; +} +``` + +The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one +defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses +the callable to produce the value and stores it for the time specified in ttl. + +so lets write our caching middleware: + +```php +final class CachingMiddleware implements MiddlewareInterface +{ + public function __construct(private CacheInterface $cache){} + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { + $key = $request->getUri()->getPath(); + return $this->cache->get($key, fn() => $handler->handle($request), 10); + } + return $handler->handle($request); + } +} +``` + +we can also modify the response after it has been created by our application, for example we could implement a gzip +middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser +know that our application is used to serve dank memes: + +```php +final class DankMemeMiddleware implements MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $response = $handler->handle($request); + return $response->withAddedHeader('Meme', 'Dank'); + } +} +``` + +but for our application we are going to just add two external middlewares: + +* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. +* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware + +```bash +composer require middlewares/trailing-slash +composer require middlewares/whoops +``` + +The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the +application as well as the middleware stack. + +Our desired request -> response flow looks something like this: + + Client + | ^ + v | + Kernel + | ^ + v | + Whoops Middleware + | ^ + v | + TrailingSlash + | ^ + v | + Routing + | ^ + v | + ContainerResolver + | ^ + v | + Controller/Action + +As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every +middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. + +```php +interface Pipeline +{ + public function dispatch(ServerRequestInterface $request): ResponseInterface; +} +``` + +And our implementation looks something like this: + +```php + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} +``` + +Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code +that should produce our response. + +In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument +and store that itself as the current tip. + +There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) + +Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline +Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline + +```php +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} +``` + +And configure the container to use the Factory to create the Pipeline: + +```php + ..., + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + ... +``` +And of course a new file called middlewares.php in our config folder: +```php +pipeline->dispatch($request); +} +``` + +Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our +pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a +dependency container, if you want you can do that as well. + +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) diff --git a/implementation/15-adding-content/phpstan-baseline.neon b/implementation/15-adding-content/phpstan-baseline.neon new file mode 100644 index 0000000..61697a1 --- /dev/null +++ b/implementation/15-adding-content/phpstan-baseline.neon @@ -0,0 +1,7 @@ +parameters: + ignoreErrors: + - + message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" + count: 1 + path: src/Http/InvokerRoutedHandler.php + diff --git a/implementation/15-adding-content/phpstan.neon b/implementation/15-adding-content/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/15-adding-content/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/15-adding-content/public/css/spectre-exp.min.css b/implementation/15-adding-content/public/css/spectre-exp.min.css new file mode 100644 index 0000000..d313774 --- /dev/null +++ b/implementation/15-adding-content/public/css/spectre-exp.min.css @@ -0,0 +1 @@ +/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/implementation/15-adding-content/public/css/spectre-icons.min.css b/implementation/15-adding-content/public/css/spectre-icons.min.css new file mode 100644 index 0000000..0276f7b --- /dev/null +++ b/implementation/15-adding-content/public/css/spectre-icons.min.css @@ -0,0 +1 @@ +/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/implementation/15-adding-content/public/css/spectre.min.css b/implementation/15-adding-content/public/css/spectre.min.css new file mode 100644 index 0000000..0fe23d9 --- /dev/null +++ b/implementation/15-adding-content/public/css/spectre.min.css @@ -0,0 +1 @@ +/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/implementation/15-adding-content/public/favicon.ico b/implementation/15-adding-content/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/15-adding-content/public/index.php b/implementation/15-adding-content/public/index.php new file mode 100644 index 0000000..d93da3a --- /dev/null +++ b/implementation/15-adding-content/public/index.php @@ -0,0 +1,5 @@ +getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render('hello', $data); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/15-adding-content/src/Action/Other.php b/implementation/15-adding-content/src/Action/Other.php new file mode 100644 index 0000000..da9ceaf --- /dev/null +++ b/implementation/15-adding-content/src/Action/Other.php @@ -0,0 +1,16 @@ +parse('This *works* **too!**'); + $response->getBody()->write($html); + return $response->withStatus(200); + } +} diff --git a/implementation/15-adding-content/src/Action/Page.php b/implementation/15-adding-content/src/Action/Page.php new file mode 100644 index 0000000..6a3aad0 --- /dev/null +++ b/implementation/15-adding-content/src/Action/Page.php @@ -0,0 +1,80 @@ +getPages(), + fn (string $filename) => str_contains($filename, $page) + ) + )[0]; + $markdown = file_get_contents($page); + + // fix the next and previous buttons to work with our routing + $markdown = preg_replace('/\(\d\d-/m', '(', $markdown); + $markdown = str_replace('.md)', ')', $markdown); + + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + $data = [ + 'title' => substr($page, 3), + 'content' => $this->parser->parse($markdown), + ]; + $html = $this->renderer->render('page/show', $data); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map(function (string $page) { + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + return [ + 'id' => substr($page, 0, 2), + 'title' => substr($page, 3), + ]; + }, $this->getPages()); + $html = $this->renderer->render('page/list', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } + + /** + * @return string[] + */ + private function getPages(): array + { + $files = glob($this->pagesPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return $files; + } +} diff --git a/implementation/15-adding-content/src/Bootstrap.php b/implementation/15-adding-content/src/Bootstrap.php new file mode 100644 index 0000000..3abc2e5 --- /dev/null +++ b/implementation/15-adding-content/src/Bootstrap.php @@ -0,0 +1,40 @@ +getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); diff --git a/implementation/15-adding-content/src/Exception/InternalServerError.php b/implementation/15-adding-content/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/15-adding-content/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +factory::fromGlobals(); + } + + /** + * @param UriInterface|string $uri + * @param array $serverParams + */ + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} diff --git a/implementation/15-adding-content/src/Factory/DoctrineEm.php b/implementation/15-adding-content/src/Factory/DoctrineEm.php new file mode 100644 index 0000000..b0be39b --- /dev/null +++ b/implementation/15-adding-content/src/Factory/DoctrineEm.php @@ -0,0 +1,32 @@ +settings->doctrine['devMode']); + + $config->setMetadataDriverImpl( + new AttributeDriver( + $this->settings->doctrine['metadataDirs'] + ) + ); + + return EntityManager::create( + $this->settings->connection, + $config, + ); + } +} diff --git a/implementation/15-adding-content/src/Factory/FileSystemSettingsProvider.php b/implementation/15-adding-content/src/Factory/FileSystemSettingsProvider.php new file mode 100644 index 0000000..f071078 --- /dev/null +++ b/implementation/15-adding-content/src/Factory/FileSystemSettingsProvider.php @@ -0,0 +1,22 @@ +filePath; + assert($settings instanceof Settings); + return $settings; + } +} diff --git a/implementation/15-adding-content/src/Factory/PipelineProvider.php b/implementation/15-adding-content/src/Factory/PipelineProvider.php new file mode 100644 index 0000000..77738f8 --- /dev/null +++ b/implementation/15-adding-content/src/Factory/PipelineProvider.php @@ -0,0 +1,25 @@ +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} diff --git a/implementation/15-adding-content/src/Factory/RequestFactory.php b/implementation/15-adding-content/src/Factory/RequestFactory.php new file mode 100644 index 0000000..2b17abc --- /dev/null +++ b/implementation/15-adding-content/src/Factory/RequestFactory.php @@ -0,0 +1,11 @@ +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = $settings; + $builder->addDefinitions($dependencies); + // $builder->enableCompilation('/tmp'); + return $builder->build(); + } +} diff --git a/implementation/15-adding-content/src/Factory/SettingsProvider.php b/implementation/15-adding-content/src/Factory/SettingsProvider.php new file mode 100644 index 0000000..ce1c5f0 --- /dev/null +++ b/implementation/15-adding-content/src/Factory/SettingsProvider.php @@ -0,0 +1,10 @@ +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + + echo $response->getBody(); + } +} diff --git a/implementation/15-adding-content/src/Http/ContainerPipeline.php b/implementation/15-adding-content/src/Http/ContainerPipeline.php new file mode 100644 index 0000000..816cedd --- /dev/null +++ b/implementation/15-adding-content/src/Http/ContainerPipeline.php @@ -0,0 +1,82 @@ + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} diff --git a/implementation/15-adding-content/src/Http/Emitter.php b/implementation/15-adding-content/src/Http/Emitter.php new file mode 100644 index 0000000..ce4c035 --- /dev/null +++ b/implementation/15-adding-content/src/Http/Emitter.php @@ -0,0 +1,10 @@ +getAttribute($this->routeAttributeName, false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} diff --git a/implementation/15-adding-content/src/Http/Pipeline.php b/implementation/15-adding-content/src/Http/Pipeline.php new file mode 100644 index 0000000..1a9dcda --- /dev/null +++ b/implementation/15-adding-content/src/Http/Pipeline.php @@ -0,0 +1,11 @@ +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} diff --git a/implementation/15-adding-content/src/Http/RoutedRequestHandler.php b/implementation/15-adding-content/src/Http/RoutedRequestHandler.php new file mode 100644 index 0000000..a7407c9 --- /dev/null +++ b/implementation/15-adding-content/src/Http/RoutedRequestHandler.php @@ -0,0 +1,10 @@ +pipeline->dispatch($request); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} diff --git a/app/src/Middleware/CacheMiddleware.php b/implementation/15-adding-content/src/Middleware/CacheMiddleware.php similarity index 100% rename from app/src/Middleware/CacheMiddleware.php rename to implementation/15-adding-content/src/Middleware/CacheMiddleware.php diff --git a/implementation/15-adding-content/src/Model/MarkdownPage.php b/implementation/15-adding-content/src/Model/MarkdownPage.php new file mode 100644 index 0000000..bae383c --- /dev/null +++ b/implementation/15-adding-content/src/Model/MarkdownPage.php @@ -0,0 +1,21 @@ +environment === 'dev'; + } +} diff --git a/app/src/Template/GithubMarkdownRenderer.php b/implementation/15-adding-content/src/Template/GithubMarkdownRenderer.php similarity index 100% rename from app/src/Template/GithubMarkdownRenderer.php rename to implementation/15-adding-content/src/Template/GithubMarkdownRenderer.php diff --git a/implementation/15-adding-content/src/Template/MarkdownParser.php b/implementation/15-adding-content/src/Template/MarkdownParser.php new file mode 100644 index 0000000..d404005 --- /dev/null +++ b/implementation/15-adding-content/src/Template/MarkdownParser.php @@ -0,0 +1,8 @@ +engine->render($template, $data); + } +} diff --git a/implementation/15-adding-content/src/Template/ParsedownParser.php b/implementation/15-adding-content/src/Template/ParsedownParser.php new file mode 100644 index 0000000..2ffd287 --- /dev/null +++ b/implementation/15-adding-content/src/Template/ParsedownParser.php @@ -0,0 +1,17 @@ +parser->parse($markdown); + } +} diff --git a/implementation/15-adding-content/src/Template/Renderer.php b/implementation/15-adding-content/src/Template/Renderer.php new file mode 100644 index 0000000..ff916ed --- /dev/null +++ b/implementation/15-adding-content/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data = []): string; +} diff --git a/implementation/15-adding-content/templates/hello.html b/implementation/15-adding-content/templates/hello.html new file mode 100644 index 0000000..15a4cd2 --- /dev/null +++ b/implementation/15-adding-content/templates/hello.html @@ -0,0 +1,6 @@ +{{> partials/head }} +
+

Hello {{name}}

+

The time is {{now}}

+
+{{> partials/foot }} diff --git a/implementation/15-adding-content/templates/page.html b/implementation/15-adding-content/templates/page.html new file mode 100644 index 0000000..c3c5284 --- /dev/null +++ b/implementation/15-adding-content/templates/page.html @@ -0,0 +1,5 @@ +{{> partials/head }} +
+ {{{content}}} +
+{{> partials/foot }} diff --git a/implementation/15-adding-content/templates/page/list.html b/implementation/15-adding-content/templates/page/list.html new file mode 100644 index 0000000..bf42348 --- /dev/null +++ b/implementation/15-adding-content/templates/page/list.html @@ -0,0 +1,19 @@ + + + + + Pages + + + +
+ +
+ + \ No newline at end of file diff --git a/implementation/15-adding-content/templates/page/show.html b/implementation/15-adding-content/templates/page/show.html new file mode 100644 index 0000000..abe295e --- /dev/null +++ b/implementation/15-adding-content/templates/page/show.html @@ -0,0 +1,17 @@ + + + + + {{title}} + + + + + + +
+ {{{content}}} +
+ + \ No newline at end of file diff --git a/implementation/15-adding-content/templates/pagelist.html b/implementation/15-adding-content/templates/pagelist.html new file mode 100644 index 0000000..538e2c4 --- /dev/null +++ b/implementation/15-adding-content/templates/pagelist.html @@ -0,0 +1,11 @@ +{{> partials/head }} +
+ +
+{{> partials/foot }} diff --git a/implementation/15-adding-content/templates/partials/foot.html b/implementation/15-adding-content/templates/partials/foot.html new file mode 100644 index 0000000..17c7245 --- /dev/null +++ b/implementation/15-adding-content/templates/partials/foot.html @@ -0,0 +1,3 @@ +
+ + \ No newline at end of file diff --git a/implementation/15-adding-content/templates/partials/head.html b/implementation/15-adding-content/templates/partials/head.html new file mode 100644 index 0000000..421d387 --- /dev/null +++ b/implementation/15-adding-content/templates/partials/head.html @@ -0,0 +1,11 @@ + + + + + No Framework: {{title}} + + + + + +
diff --git a/implementation/16-data-repository/.php-cs-fixer.php b/implementation/16-data-repository/.php-cs-fixer.php new file mode 100644 index 0000000..705a7d7 --- /dev/null +++ b/implementation/16-data-repository/.php-cs-fixer.php @@ -0,0 +1,38 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config' + ]) + ); \ No newline at end of file diff --git a/implementation/16-data-repository/.phpcs.xml.dist b/implementation/16-data-repository/.phpcs.xml.dist new file mode 100644 index 0000000..3b433f6 --- /dev/null +++ b/implementation/16-data-repository/.phpcs.xml.dist @@ -0,0 +1,9 @@ + + + + + src + config + + + \ No newline at end of file diff --git a/implementation/16-data-repository/cli-config.php b/implementation/16-data-repository/cli-config.php new file mode 100644 index 0000000..fbc6598 --- /dev/null +++ b/implementation/16-data-repository/cli-config.php @@ -0,0 +1,13 @@ +getContainer(); + +return ConsoleRunner::createHelperSet($container->get(EntityManagerInterface::class)); diff --git a/implementation/16-data-repository/composer.json b/implementation/16-data-repository/composer.json new file mode 100644 index 0000000..b5c7f1a --- /dev/null +++ b/implementation/16-data-repository/composer.json @@ -0,0 +1,54 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3", + "mustache/mustache": "^2.14", + "psr/http-server-middleware": "^1.0", + "middlewares/trailing-slash": "^2.0", + "middlewares/whoops": "^2.0", + "erusev/parsedown": "^1.7", + "league/commonmark": "^2.2" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require-dev": { + "phpstan/phpstan": "^1.5", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0", + "squizlabs/php_codesniffer": "^3.6", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.1", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "mnapoli/hard-mode": "^0.3.0" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1234 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + } +} diff --git a/implementation/16-data-repository/composer.lock b/implementation/16-data-repository/composer.lock new file mode 100644 index 0000000..a62d9c7 --- /dev/null +++ b/implementation/16-data-repository/composer.lock @@ -0,0 +1,2438 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "00acf07ae222f9117a84bce157b99837", + "packages": [ + { + "name": "dflydev/dot-access-data", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^3.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" + }, + "time": "2021-08-13T13:06:58+00:00" + }, + { + "name": "erusev/parsedown", + "version": "1.7.4", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "support": { + "issues": "https://github.com/erusev/parsedown/issues", + "source": "https://github.com/erusev/parsedown/tree/1.7.x" + }, + "time": "2019-12-30T22:54:17+00:00" + }, + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-03-29T20:12:16+00:00" + }, + { + "name": "league/commonmark", + "version": "2.2.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", + "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.15" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.30.0", + "commonmark/commonmark.js": "0.30.0", + "composer/package-versions-deprecated": "^1.8", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4", + "phpstan/phpstan": "^0.12.88 || ^1.0.0", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2022-02-26T21:24:45+00:00" + }, + { + "name": "league/config", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.90", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2021-08-14T12:15:32+00:00" + }, + { + "name": "middlewares/trailing-slash", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/middlewares/trailing-slash.git", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", + "shasum": "" + }, + "require": { + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to normalize the trailing slash of the uri path", + "homepage": "https://github.com/middlewares/trailing-slash", + "keywords": [ + "http", + "middleware", + "normalize", + "path", + "psr-15", + "psr-7", + "slash" + ], + "support": { + "issues": "https://github.com/middlewares/trailing-slash/issues", + "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" + }, + "time": "2020-12-02T00:06:55+00:00" + }, + { + "name": "middlewares/utils", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/middlewares/utils.git", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v2.16", + "guzzlehttp/psr7": "^2.0", + "laminas/laminas-diactoros": "^2.4", + "nyholm/psr7": "^1.0", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "slim/psr7": "^1.4", + "squizlabs/php_codesniffer": "^3.5", + "sunrise/http-message": "^1.0", + "sunrise/http-server-request": "^1.0", + "sunrise/stream": "^1.0.15", + "sunrise/uri": "^1.0.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\Utils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Common utils for PSR-15 middleware packages", + "homepage": "https://github.com/middlewares/utils", + "keywords": [ + "PSR-11", + "http", + "middleware", + "psr-15", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/middlewares/utils/issues", + "source": "https://github.com/middlewares/utils/tree/v3.3.0" + }, + "time": "2021-07-04T17:56:23+00:00" + }, + { + "name": "middlewares/whoops", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/middlewares/whoops.git", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.5", + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^5.0 || ^7.0", + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to use Whoops as error handler", + "homepage": "https://github.com/middlewares/whoops", + "keywords": [ + "error", + "http", + "middleware", + "psr-15", + "psr-7", + "server", + "whoops" + ], + "support": { + "issues": "https://github.com/middlewares/whoops/issues", + "source": "https://github.com/middlewares/whoops/tree/v2.0.2" + }, + "time": "2022-01-27T20:31:30+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nette/schema", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", + "shasum": "" + }, + "require": { + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": ">=7.1 <8.2" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "phpstan/phpstan-nette": "^0.12", + "tracy/tracy": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.2.2" + }, + "time": "2021-10-15T11:40:02+00:00" + }, + { + "name": "nette/utils", + "version": "v3.2.7", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", + "shasum": "" + }, + "require": { + "php": ">=7.2 <8.2" + }, + "conflict": { + "nette/di": "<3.0.6" + }, + "require-dev": { + "nette/tester": "~2.0", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v3.2.7" + }, + "time": "2022-01-24T11:29:14+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/master" + }, + "time": "2018-10-30T17:12:04+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "shasum": "" + }, + "require": { + "php": ">=8.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:55:41+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-04T08:16:47+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "doctrine/coding-standard", + "version": "8.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/coding-standard.git", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Müller", + "email": "st.mueller@dzh-online.de" + } + ], + "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "doctrine", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/doctrine/coding-standard/issues", + "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" + }, + "time": "2021-04-03T10:54:55+00:00" + }, + { + "name": "mnapoli/hard-mode", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/hard-mode.git", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", + "shasum": "" + }, + "require": { + "doctrine/coding-standard": "^8.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Hard mode for PHP", + "support": { + "issues": "https://github.com/mnapoli/hard-mode/issues", + "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" + }, + "time": "2020-10-12T07:54:37+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.5.4", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", + "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.5.4" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-04-03T12:39:00+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^1.2.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" + }, + "time": "2021-11-18T09:30:29+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/16-data-repository/config/dependencies.php b/implementation/16-data-repository/config/dependencies.php new file mode 100644 index 0000000..0040933 --- /dev/null +++ b/implementation/16-data-repository/config/dependencies.php @@ -0,0 +1,55 @@ + fn (SystemClockNow $n) => $n, + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, + Emitter::class => fn (BasicEmitter $e) => $e, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, + CacheInterface::class => fn (FilesystemAdapter $a) => $a, + MarkdownParser::class => fn (ParsedownParser $p) => $p, + MarkdownPageRepo::class => fn (FileSystemMarkdownPageRepo $r) => $r, + + // Factories + ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), + Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), +]; diff --git a/implementation/16-data-repository/config/middlewares.php b/implementation/16-data-repository/config/middlewares.php new file mode 100644 index 0000000..71dd461 --- /dev/null +++ b/implementation/16-data-repository/config/middlewares.php @@ -0,0 +1,11 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/page', [Page::class, 'list']); + $r->addRoute('GET', '/page/{page}', [Page::class, 'show']); + $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); + $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/implementation/16-data-repository/config/settings.php b/implementation/16-data-repository/config/settings.php new file mode 100644 index 0000000..c654565 --- /dev/null +++ b/implementation/16-data-repository/config/settings.php @@ -0,0 +1,12 @@ +>](02-composer.md) + +### Front Controller + +A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. + +To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. + +A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. + +The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. + +But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. + +So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. + +Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: + +```php +>](02-composer.md) diff --git a/implementation/16-data-repository/data/pages/02-composer.md b/implementation/16-data-repository/data/pages/02-composer.md new file mode 100644 index 0000000..a25a4a8 --- /dev/null +++ b/implementation/16-data-repository/data/pages/02-composer.md @@ -0,0 +1,75 @@ +[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) + +### Composer + +[Composer](https://getcomposer.org/) is a dependency manager for PHP. + +Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do +something. With Composer, you can install third-party libraries for your application. + +If you don't have Composer installed already, head over to the website and install it. You can find Composer packages +for your project on [Packagist](https://packagist.org/). + +Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will +be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. + +Add the following content to the file: + +```json +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubiana", + "email": "lubiana@hannover.ccc.de" + } + ] +} +``` + +In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use +whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. +Just replace it with your namespace in your own code. + +I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. + +As the Bootstrap.php file is placed in that directory we should +add the namespace to the File as well. Here is my current Bootstrap.php +as a reference: + +```php +>](03-error-handler.md) diff --git a/implementation/16-data-repository/data/pages/03-error-handler.md b/implementation/16-data-repository/data/pages/03-error-handler.md new file mode 100644 index 0000000..60465d0 --- /dev/null +++ b/implementation/16-data-repository/data/pages/03-error-handler.md @@ -0,0 +1,79 @@ +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) + +### Error Handler + +An error handler allows you to customize what happens if your code results in an error. + +A nice error page with a lot of information for debugging goes a long way during development. So the first package +for your application will take care of that. + +I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. +If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, +you have total control over your project. + +An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) + +To install a new package, open up your `composer.json` and add the package to the require part. It should now look +like this: + +```php +"require": { + "php": ">=8.1.0", + "filp/whoops": "^2.14" +}, +``` + +Now run `composer update` in your console and it will be installed. + +Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, +i that case composer automatically installs the package and updates your composer.json-file. + +But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, +ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you +only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. + +**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message +can help someone to gain access to your system. Always show a user friendly error page instead and send an email to +yourself, write to a log or something similar. So only you can see the errors in the production environment. + +For development that does not make sense though -- you want a nice error page. The solution is to have an environment +switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in +case no environment has been set. + +Then after the error handler registration, throw an `Exception` to test if everything is working correctly. +Your `Bootstrap.php` should now look similar to this: + +```php +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (\Throwable $e) { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +throw new \Exception("Ooooopsie"); + +``` + +You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until +you get it working. Now would also be a good time for another commit. + + +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/implementation/16-data-repository/data/pages/04-development-helpers.md b/implementation/16-data-repository/data/pages/04-development-helpers.md new file mode 100644 index 0000000..9505284 --- /dev/null +++ b/implementation/16-data-repository/data/pages/04-development-helpers.md @@ -0,0 +1,260 @@ +[<< previous](03-error-handler.md) | [next >>](05-http.md) + +### Development Helpers + +I have added some more helpers to my composer.json that help me with development. As these are scripts and programms +used only for development they should not be used in a production environment. Composer has a specific sections in its +file called "dev-dependencies", everything that is required in this section does not get installen in production. + +Let's install our dev-helpers and i will explain them one by one: +`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` + +#### Static Code Analysis with phpstan + +Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or +create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements +that are not (or not always) available, if-statements that always are true and a lot of other stuff. + +A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. + +```php +/** + * @param \DateTime $date + * @return void + */ +function printDate($date) { + $date->format('Y-m-d H:i:s'); +} + +printDate('now'); +``` +if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` + +It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has +no use, and secondly, that we are calling the function with a string instead of a datetime object. + +```shell +1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + ------ --------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ --------------------------------------------------------------------------------------------- +30 Call to method DateTime::format() on a separate line has no effect. +33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. + ------ --------------------------------------------------------------------------------------------- +``` + +The second error is something that "declare strict-types" already catches for us, but the first error is something that +we usually would not discover easily without speccially looking for this errortype. + +We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and +path everytime we want to check our code for errors: + +```yaml +parameters: + level: max + paths: + - src +``` +now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project + +With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us +some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. + +```shell +composer require --dev phpstan/extension-installer +composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules +``` + +During the first install you need to allow the extension installer to actually install the extension. The second command +installs some more strict rulesets and activates them in phpstan. + +If we now rerun phpstan it already tells us about some errors we have made: + +``` + ------ ----------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ ----------------------------------------------------------------------------------------------- +10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider + using long ternary. +25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: + http://bit.ly/subtypeexception +26 Unreachable statement - code above always terminates. + ------ ----------------------------------------------------------------------------------------------- +``` + +The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove +that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific +problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working +on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages +everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we +are adding to our code. + +In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the +phpstan.neon file and run phpstan with the +'--generate-baseline' option: + +```yaml +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` +```shell +[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline +Note: Using configuration file /home/vagrant/app/phpstan.neon. + 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + + + [OK] Baseline generated with 1 error. + + +``` + +you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) + +#### PHP-CS-Fixer + +Another great tool is the php-cs-fixer, which just applies a specific style to your code. + +when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current +directory. + +You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) + +personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup +a configuration file that i use in all my projects .php-cs-fixer.php: + +```php +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + ]) + ); +``` + +#### PHP Codesniffer + +The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra +rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. + +it provides the `phpcs` command to check for violations and the `phpcbf` command to actually fix most of the violations. + +Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have +guessed we are adding some extra rules. + +Lets install the ruleset with composer +```shell +composer require --dev mnapoli/hard-mode +``` + +and add a configuration file to actually use it '.phpcs.xml.dist' +```xml + + + + + src + + + +``` + +running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. + +``` +[vagrant@archlinux app]$ ./vendor/bin/phpcs + +FILE: src/Bootstrap.php +---------------------------------------------------------------------------------------------------- +FOUND 4 ERRORS AFFECTING 4 LINES +---------------------------------------------------------------------------------------------------- + 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. + 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead +---------------------------------------------------------------------------------------------------- +PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY +---------------------------------------------------------------------------------------------------- + +Time: 639ms; Memory: 10MB +``` + +You can then use `./vendor/bin/phpcbf` to try to fix them. + + +#### Symfony Var-Dumper + +another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small +functions. + +dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects +or arrays. + +dd() on the other hand is a function that dumps its parameters and then exits the php-script. + +you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. + +#### Composer scripts + +now we have a few commands that are available on the command line. i personally do not like to type complex commands +with lots of parameters by hand all the time, so i added a few lines to my composer.json: + +```json +"scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" +}, +``` + +that way i can just type "composer" followed by the command name in the root of my project. if i want to start the +php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the +time. + +You could also configure PhpStorm to automatically run these commands in the background and highlight the violations +directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my +flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. + +My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before +commiting and pushing the code. + +[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/implementation/16-data-repository/data/pages/05-http.md b/implementation/16-data-repository/data/pages/05-http.md new file mode 100644 index 0000000..6166214 --- /dev/null +++ b/implementation/16-data-repository/data/pages/05-http.md @@ -0,0 +1,124 @@ +[<< previous](04-development-helpers.md) | [next >>](06-router.md) + +### HTTP + +PHP already has a few things built in to make working with HTTP easier. For example there are the +[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. + +These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, +if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, +then you will want a class with a nice object-oriented interface that you can use in your application instead. + +Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The +standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php +projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. + +As this is a widely adopted standard there are already several implementations available for us to use. I will choose +the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. + +Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a +[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. + +Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use +that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding +the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). + + +to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit +enter + +Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): + +```php +$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); +$response = new \Laminas\Diactoros\Response; +$response->getBody()->write('Hello World! '); +$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); +``` + +This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. + +In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the +write()-Method on that object. + + +To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: + +```php +echo $response->getBody(); +``` + +This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only +stores data. + +You can play around with the other methods of the Request object and take a look at its content with the dd() function. + +```php +dd($response) +``` + +Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot +be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which +creates a copy of the request object with the changed property and returns that clone: + +```php +$response = $response->withStatus(200); +$response = $response->withAddedHeader('Content-type', 'application/json'); +``` + +If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been +defined this way. + +But if you have been keeping attention you might argue that the following line should not work if the request object is +immutable. + +```php +$response->getBody()->write('Hello World!'); +``` + +The response-body implements a stream interface which is immutable for some reasons that are described in the +[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be +aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in +the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. +I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content +to a response object. + +```php +$body = $response->getBody(); +$body->write('Hello World!'); +$response = $response->withBody($body); +``` + +Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our +output-logic a little bit more. Replace the line that echos the response-body with the following: + +```php +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); +``` + +This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a +webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. + +Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. + +Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter + + +[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/implementation/16-data-repository/data/pages/06-router.md b/implementation/16-data-repository/data/pages/06-router.md new file mode 100644 index 0000000..6c39ae5 --- /dev/null +++ b/implementation/16-data-repository/data/pages/06-router.md @@ -0,0 +1,101 @@ +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) + +### Router + +A router dispatches to different handlers depending on rules that you have set up. + +With your current setup it does not matter what URL is used to access the application, it will always result in the same +response. So let's fix that now. + +I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own +favorite package. + +Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) + +By now you know how to install Composer packages, so I will leave that to you. + +Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. + +```php +$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +switch ($routeInfo[0]) { + case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: + $response = (new \Laminas\Diactoros\Response)->withStatus(405); + $response->getBody()->write('Method not allowed'); + $response = $response->withStatus(405); + break; + case \FastRoute\Dispatcher::FOUND: + $handler = $routeInfo[1]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + /** @var \Psr\Http\Message\ResponseInterface $response */ + $response = call_user_func($handler, $request); + break; + case \FastRoute\Dispatcher::NOT_FOUND: + default: + $response = (new \Laminas\Diactoros\Response)->withStatus(404); + $response->getBody()->write('Not Found!'); + break; +} +``` + +In the first part of the code, you are registering the available routes for your application. In the second part, the +dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, +we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. +If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. + +This setup might work for really small applications, but once you start adding a few routes your bootstrap file will +quickly get cluttered. So let's move them out into a separate file. + +Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; + +```php +addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}; +``` + +Now let's rewrite the route dispatcher part to use the `Routes.php` file. + +```php +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); +``` + +This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. + +Of course we now need to add the 'config' folder to the configuration files of our +devhelpers so that they can scan that directory as well. + +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/implementation/16-data-repository/data/pages/07-dispatching-to-a-class.md b/implementation/16-data-repository/data/pages/07-dispatching-to-a-class.md new file mode 100644 index 0000000..0c961a4 --- /dev/null +++ b/implementation/16-data-repository/data/pages/07-dispatching-to-a-class.md @@ -0,0 +1,137 @@ +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) + +### Dispatching to a Class + +In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). +MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to +learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) +and the followup posts. + +So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). + +We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other +common names are 'Controllers' or 'Actions'. + +Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. +In there, create a `Hello.php` file. + +```php +getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + } +} +``` + +You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) +that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is +fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement +it in some other parts of our application as well. In order to use that Interface we have to require it with composer: +'composer require psr/http-server-handler'. + +The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. +At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. + +Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: + +```php +return function(\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); + $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); +}; +``` + +Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared +the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing +the response to the one we defined for the second route. + +To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: + +```php +case \FastRoute\Dispatcher::FOUND: + $handler = new $routeInfo[1]; + if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { + throw new \Exception('Invalid Requesthandler'); + } + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + assert($response instanceof \Psr\Http\Message\ResponseInterface) + break; +``` + +So instead of just calling a method you are now instantiating an object and then calling the method on it. + +Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. + +And of course don't forget to commit your changes. + +Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still +generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for +example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still +have our whoopsie error-handler but i like to be more explicit in my control flow. + +In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new +Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and +InternalServerError. All three should extend phps Base Exception class. + +Here is my NotFound.php for example. + +```php + $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody()->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} +``` + +Check if our code still works, try to trigger some errors, run phpstan and the fix command +and don't forget to commit your changes. + +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/implementation/16-data-repository/data/pages/08-inversion-of-control.md b/implementation/16-data-repository/data/pages/08-inversion-of-control.md new file mode 100644 index 0000000..21f4f23 --- /dev/null +++ b/implementation/16-data-repository/data/pages/08-inversion-of-control.md @@ -0,0 +1,54 @@ +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) + +### Inversion of Control + +In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we +want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then +we would need to edit every one of our request handlers to call a different constructor of the class. + +The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that +instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done +with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). + +If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is +implemented, it will make sense. + +Change your `Hello` action to the following: + +```php +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: + +```php +$handler = new $className($response); +``` + +Of course we need to also update all the other handlers. + +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/implementation/16-data-repository/data/pages/09-dependency-injector.md b/implementation/16-data-repository/data/pages/09-dependency-injector.md new file mode 100644 index 0000000..7f7c6a2 --- /dev/null +++ b/implementation/16-data-repository/data/pages/09-dependency-injector.md @@ -0,0 +1,213 @@ +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) + +### Dependency Injector + +A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when +the class is instantiated. + +Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work +with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look +for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). + +I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) +out of the box. + +After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: + +```php +addDefinitions([ + \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), +]); + +return $builder->build(); +``` + +In this file we create a containerbuilder, add some definitions to it and return the container. +As the container supports autowiring we only need to define services where we want to use a specific implementation of +an interface. + +In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to +tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second +exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. + +Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), +as we will use that extensively. + +Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally +to create our Handler-Object + +```php +$container = require __DIR__ . '/../config/dependencies.php'; +assert($container instanceof \Psr\Container\ContainerInterface); + +$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); +assert($request instanceof \Psr\Http\Message\ServerRequestInterface); +``` + +The other part that has to be changed is the dispatching of the route. Before you had the following code: + +```php +$className = $routeInfo[1]; +$handler = new $className($response); +assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Change that to the following: + +```php +/** @var RequestHandlerInterface $handler */ +$className = $routeInfo[1]; +$handler = $container->get($className); +assert($handler instanceof RequestHandlerInterface); +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Make sure to use the container fetch the response object in the catch blocks as well: + +```php +} catch (MethodNotAllowed) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(404); + $response->getBody()->write('Not Found'); +} +``` + +Now all your controller constructor dependencies will be automatically resolved with PHP-DI. + +We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons +in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call +`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we +want to work with a different date in a test. + +Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation +that creates us a DateTimeImmutable object with the current date and time. + +src/Service/Time/Now.php: +```php +namespace Lubian\NoFramework\Service\Time; + +interface Now +{ + public function __invoke(): \DateTimeImmutable; +} +``` +src/Service/Time/SystemClockNow.php: +```php +namespace Lubian\NoFramework\Service\Time; + +final class SystemClockNow implements Now +{ + + public function __invoke(): \DateTimeImmutable + { + return new \DateTimeImmutable; + } +} +``` +If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and +update the handle-method to use the new class property: + +```php +getAttribute('name', 'Stranger'); + $nowAsString = ($this->now)()->format('H:i:s'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowAsString); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI +automatically figures out what classes are requested in the constructor and tries to create the objects needed. + +But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred +S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: + +```php + public function __construct( + private ResponseInterface $response, + private Now $now, + ) +``` + +When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation +should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: + +```php +\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), +``` + +we could also use the PHP-DI create method to delegate the object creation to the container implementation: +```php +\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), +``` + +this way the container can try to resolve any dependencies that the class might have internally, but prefer the other +method because we are not depending on this specific dependency injection implementation. + +Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are +requesting the Hello action. + +If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As +we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way +we wont get any warnings for this particular issue, but for any other issues we add to our code. + +Update the phpstan.neon file to include a "baseline" file: + +``` +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` + +if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and +ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just +'baseline' + +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/implementation/16-data-repository/data/pages/10-invoker.md b/implementation/16-data-repository/data/pages/10-invoker.md new file mode 100644 index 0000000..3033fae --- /dev/null +++ b/implementation/16-data-repository/data/pages/10-invoker.md @@ -0,0 +1,102 @@ +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) + +### Invoker + +Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the +one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long +with some services and not the whole Requestobject with all its various properties. + +If we take our Hello action for example we only need a response object, the time service and the 'name' information from +the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named +the class hello and it would be redundant to also call the the method hello. So an updated version of that class could +look like this: + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + string $name = 'Stranger', + ): ResponseInterface + { + $body = $this->response->getBody(); + $nowString = $now->get()->format('H:i:s'); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowString); + return $response + ->withBody($body) + ->withStatus(200); + } +} +``` + +It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short +closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the +rootpath of our application yet. + +```php +$r->addRoute('GET', '/hello[/{name}]', Hello::class); +$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); +$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +``` + +In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the +route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that +class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what +arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router +if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple +callable we would need to manually use reflection as well to resolve all the arguments. + +But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) +which handles all of that for us. + +After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo +switch section in the Bootstrap.php file to use the container->call() method: + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2]; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$response = $container->call($handler, $args); +``` + +Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. + +But by now you should know that I do not like to depend on specific implementations and the call method is not defined in +the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container +or any other implementation. + +Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific +container implementation so we could use it with any container that implements the ContainerInterface. And best of all +the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that +we could implement if we ever want to write our own implementation or we could write an adapter that uses a different +class that solves the same problem. + +But for now we are using the solution provided by PHP-DI. +So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2] ?? []; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$invoker = $container->get(InvokerInterface::class); +assert($invoker instanceof InvokerInterface); +$response = $invoker->call($handler, $args); +assert($response instanceof ResponseInterface); +``` + +Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) +by php, and even some more. + +But let us move on to something more fun and add some templating functionality to our application as we are trying to build +a website in the end. + +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/implementation/16-data-repository/data/pages/11-templating.md b/implementation/16-data-repository/data/pages/11-templating.md new file mode 100644 index 0000000..7bfe1aa --- /dev/null +++ b/implementation/16-data-repository/data/pages/11-templating.md @@ -0,0 +1,236 @@ +[<< previous](10-invoker.md) | [next >>](12-configuration.md) + +### Templating + +A template engine is not necessary with PHP because the language itself can take care of that. But it can make things +like escaping values easier. They also make it easier to draw a clear line between your application logic and the +template files which should only put your variables into the HTML code. + +A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please +also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. +Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. + +For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install +that package before you continue (`composer require mustache/mustache`). + +Another well known alternative would be [Twig](http://twig.sensiolabs.org/). + +Now please go and have a look at the source code of the +[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class +does not implement an interface. + +You could just type hint against the concrete class. But the problem with this approach is that you create tight +coupling. + +In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the +implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want +to add functionality to the engine. You can't do that without going back and changing all your code that is tightly +coupled. + +What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need +another implementation, you just implement that interface in your new class and inject the new class instead. + +Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). +This sounds a lot more complicated than it is, so just follow along. + +First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). +This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. +A class can implement multiple interfaces if necessary. + +So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a +new folder in your `src/` folder with the name `Template` where you can put all the template related things. + +In there create a new interface `Renderer.php` that looks like this: + +```php + $data */ + public function render(string $template, array $data = []) : string; +} +``` + +Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file +`MustacheRenderer.php` with the following content: + +```php +engine->render($template, $data); + } +} +``` + +As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple +and only fulfills the interface. + +Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know +which implementation he has to inject when you hint for the interface. Add this line: + +```php +[ + ... + \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) + ->constructor(new Mustache_Engine), +] +``` + +Now update the Hello.php class to require an implementation of our renderer interface +and use that to render a string using mustache syntax. + + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + Renderer $renderer, + string $name = 'Stranger', + ): ResponseInterface { + $body = $response->getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render( + 'Hello {{name}}, the time is {{now}}!', + $data, + ); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} +``` + +Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. +But what we want is template files, so let's go back and change that. + +To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the +`dependencies.php` file and add the following code: + +```php +[ + ... + Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), + Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), +] +``` + +We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. +Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have +to rename all the template files. + +To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the +dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader +in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object +we can then use it in the factory. + +In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: + +``` +

Hello World

+Hello {{ name }} +``` + +Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` + +Navigate to the hello page in your browser to make sure everything works. + +One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies +file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration +values. + +Lets create a 'Settings' class in our './src' Folder: + +```php +addDefinitions([ + Settings::class => fn () => require __DIR__ '/settings.php', + ResponseInterface::class => create(Response::class), + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + Renderer::class => fn (ME $me) => new Mustache($me), + MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), +]); + +return $builder->build(); +``` + + + +And as always, don't forget to commit your changes. + + +[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/implementation/16-data-repository/data/pages/12-configuration.md b/implementation/16-data-repository/data/pages/12-configuration.md new file mode 100644 index 0000000..a44dfd5 --- /dev/null +++ b/implementation/16-data-repository/data/pages/12-configuration.md @@ -0,0 +1,201 @@ +[<< previous](11-templating.md) | [next >>](13-refactoring.md) + +### Configuration + +In the last chapter we added some more definitions to our dependencies.php in that definitions +we needed to pass quite a few configuration settings and filesystem strings to the constructors +of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. + +As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. + +As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. + +Lets create a 'Settings' class in our './src' Folder: + +```php +filePath; + } +} +``` + +If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files +and craft a settings object from them. + +As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another +factory for our Container because everyone should have a Friend :) + +```php +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn () => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} +``` + +For this to work we need to change our dependencies.php file to just return the array of definitions: +And here we can instantly use the Settings object to create our template engine. + +```php + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), +]; +``` + +Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: +require __DIR__ . '/../vendor/autoload.php'; + +```php +... +error_reporting(E_ALL); + +$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); +$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); +... +``` + +Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. + +[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/implementation/16-data-repository/data/pages/13-refactoring.md b/implementation/16-data-repository/data/pages/13-refactoring.md new file mode 100644 index 0000000..067e168 --- /dev/null +++ b/implementation/16-data-repository/data/pages/13-refactoring.md @@ -0,0 +1,377 @@ +[<< previous](12-configuration.md) | [next >>](14-middleware.md) + +### Refactoring + +By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no +reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. +After all the bootstrap file should just set up the classes needed for the handling logic and execute them. + +At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. +As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the +method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non +static and extracted an interface. + +'./src/Http/Emitter.php' +```php +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + echo $response->getBody(); + } +} +``` +After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following +code in the Bootstrap.php to emit the response: + +```php +/** @var Emitter $emitter */ +$emitter = $container->get(Emitter::class); +$emitter->emit($response); +``` + +If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily +write an adapter that implements your emitter interface and wraps that more advanced emitter + +Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and +calling the routerhandler that in the passes the request to a function and gets the response. + +For this to steps to be seperated we are going to create two more classes: +1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object +2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the + requestobject, fetches the correct object from the container and calls it to create a response. + +Lets create the HandlerInterface first: + +```php +getAttribute($this->routeAttributeName, false); + assert($handler !== false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} + +``` + +We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. +The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler +as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in +the next chapter. + +```php +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} +``` + +Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. +```php +[ + '...', + Emitter::class => fn (BasicEmitter $e) => $e, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, +], +``` + +And then we can update our Bootstrap.php to fetch all the services and let them handle the request. + +```php +... +$routeMiddleWare = $container->get(MiddlewareInterface::class); +assert($routeMiddleWare instanceof MiddlewareInterface); +$handler = $container->get(RoutedRequestHandler::class); +assert($handler instanceof RequestHandlerInterface); +$emitter = $container->get(Emitter::class); +assert($emitter instanceof Emitter); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$response = $routeMiddleWare->process($request, $handler); +$emitter->emit($response); +``` +Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of +code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). + +So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. + +I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class +should require to function properly. + +* A RequestFactory + We want our Kernel to be able to build the request itself +* An Emitter + Without an Emitter we will not be able to send the response to the client +* RouteMiddleware + To decore the request with the correct handler for the requested route +* RequestHandler + To delegate the request to the correct funtion that creates the response + +As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface +to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our +interface: + +```php +factory::fromGlobals(); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} +``` + +For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: + +```php +routeMiddleware->process($request, $this->handler); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} + +``` + +We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines + +```php +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); +``` + +You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking +at the Whoops output and adding the needed definitions to the dependencies.php file. + +And as always, don't forget to commit your changes. + +[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/implementation/16-data-repository/data/pages/14-middleware.md b/implementation/16-data-repository/data/pages/14-middleware.md new file mode 100644 index 0000000..e698327 --- /dev/null +++ b/implementation/16-data-repository/data/pages/14-middleware.md @@ -0,0 +1,298 @@ +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) + +### Middleware + +In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain +a bit more about what this interface does and why it is used in many applications. + +The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets +passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all +the middlewars to the client/emitter. + +So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the +response after it gets created by our handlers. + +So lets take a look at the middleware and the requesthandler interfaces + +```php +interface MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; +} + +interface RequestHandlerInterface +{ + public function handle(ServerRequestInterface $request): ResponseInterface; +} +``` + +The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a +requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the +response. + +But the middleware could just ignore the handler and produce a response on its own as the interface just requires us +to produce a response. + +A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users +that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the +database. + +In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates +the request with an 'isAuthenticated' attribute. + +If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that +response is not already cached, than we let the handler create the response and store that in the cache for a few +seconds + +```php +interface CacheInterface +{ + public function get(string $key, callable $resolver, int $ttl): mixed; +} +``` + +The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one +defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses +the callable to produce the value and stores it for the time specified in ttl. + +so lets write our caching middleware: + +```php +final class CachingMiddleware implements MiddlewareInterface +{ + public function __construct(private CacheInterface $cache){} + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { + $key = $request->getUri()->getPath(); + return $this->cache->get($key, fn() => $handler->handle($request), 10); + } + return $handler->handle($request); + } +} +``` + +we can also modify the response after it has been created by our application, for example we could implement a gzip +middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser +know that our application is used to serve dank memes: + +```php +final class DankMemeMiddleware implements MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $response = $handler->handle($request); + return $response->withAddedHeader('Meme', 'Dank'); + } +} +``` + +but for our application we are going to just add two external middlewares: + +* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. +* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware + +```bash +composer require middlewares/trailing-slash +composer require middlewares/whoops +``` + +The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the +application as well as the middleware stack. + +Our desired request -> response flow looks something like this: + + Client + | ^ + v | + Kernel + | ^ + v | + Whoops Middleware + | ^ + v | + TrailingSlash + | ^ + v | + Routing + | ^ + v | + ContainerResolver + | ^ + v | + Controller/Action + +As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every +middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. + +```php +interface Pipeline +{ + public function dispatch(ServerRequestInterface $request): ResponseInterface; +} +``` + +And our implementation looks something like this: + +```php + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} +``` + +Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code +that should produce our response. + +In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument +and store that itself as the current tip. + +There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) + +Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline +Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline + +```php +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} +``` + +And configure the container to use the Factory to create the Pipeline: + +```php + ..., + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + ... +``` +And of course a new file called middlewares.php in our config folder: +```php +pipeline->dispatch($request); +} +``` + +Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our +pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a +dependency container, if you want you can do that as well. + +[<< previous](12-refactoring.md) | [next >>](14-invoker.md) diff --git a/implementation/16-data-repository/phpstan-baseline.neon b/implementation/16-data-repository/phpstan-baseline.neon new file mode 100644 index 0000000..61697a1 --- /dev/null +++ b/implementation/16-data-repository/phpstan-baseline.neon @@ -0,0 +1,7 @@ +parameters: + ignoreErrors: + - + message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" + count: 1 + path: src/Http/InvokerRoutedHandler.php + diff --git a/implementation/16-data-repository/phpstan.neon b/implementation/16-data-repository/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/16-data-repository/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/16-data-repository/public/css/spectre-exp.min.css b/implementation/16-data-repository/public/css/spectre-exp.min.css new file mode 100644 index 0000000..d313774 --- /dev/null +++ b/implementation/16-data-repository/public/css/spectre-exp.min.css @@ -0,0 +1 @@ +/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/implementation/16-data-repository/public/css/spectre-icons.min.css b/implementation/16-data-repository/public/css/spectre-icons.min.css new file mode 100644 index 0000000..0276f7b --- /dev/null +++ b/implementation/16-data-repository/public/css/spectre-icons.min.css @@ -0,0 +1 @@ +/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/implementation/16-data-repository/public/css/spectre.min.css b/implementation/16-data-repository/public/css/spectre.min.css new file mode 100644 index 0000000..0fe23d9 --- /dev/null +++ b/implementation/16-data-repository/public/css/spectre.min.css @@ -0,0 +1 @@ +/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/implementation/16-data-repository/public/favicon.ico b/implementation/16-data-repository/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/16-data-repository/public/index.php b/implementation/16-data-repository/public/index.php new file mode 100644 index 0000000..d93da3a --- /dev/null +++ b/implementation/16-data-repository/public/index.php @@ -0,0 +1,5 @@ +getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render('hello', $data); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/16-data-repository/src/Action/Other.php b/implementation/16-data-repository/src/Action/Other.php new file mode 100644 index 0000000..da9ceaf --- /dev/null +++ b/implementation/16-data-repository/src/Action/Other.php @@ -0,0 +1,16 @@ +parse('This *works* **too!**'); + $response->getBody()->write($html); + return $response->withStatus(200); + } +} diff --git a/implementation/16-data-repository/src/Action/Page.php b/implementation/16-data-repository/src/Action/Page.php new file mode 100644 index 0000000..4af45f0 --- /dev/null +++ b/implementation/16-data-repository/src/Action/Page.php @@ -0,0 +1,60 @@ +repo->byName($page); + + // fix the next and previous buttons to work with our routing + $content = preg_replace('/\(\d\d-/m', '(', $page->content); + assert(is_string($content)); + $content = str_replace('.md)', ')', $content); + + $data = [ + 'title' => $page->title, + 'content' => $this->parser->parse($content), + ]; + + $html = $this->renderer->render('page/show', $data); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map(function (MarkdownPage $page) { + return [ + 'id' => $page->id, + 'title' => $page->content, + ]; + }, $this->repo->all()); + + $html = $this->renderer->render('page/list', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } +} diff --git a/implementation/16-data-repository/src/Bootstrap.php b/implementation/16-data-repository/src/Bootstrap.php new file mode 100644 index 0000000..3abc2e5 --- /dev/null +++ b/implementation/16-data-repository/src/Bootstrap.php @@ -0,0 +1,40 @@ +getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); diff --git a/implementation/16-data-repository/src/Exception/InternalServerError.php b/implementation/16-data-repository/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/16-data-repository/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +factory::fromGlobals(); + } + + /** + * @param UriInterface|string $uri + * @param array $serverParams + */ + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} diff --git a/implementation/16-data-repository/src/Factory/DoctrineEm.php b/implementation/16-data-repository/src/Factory/DoctrineEm.php new file mode 100644 index 0000000..b0be39b --- /dev/null +++ b/implementation/16-data-repository/src/Factory/DoctrineEm.php @@ -0,0 +1,32 @@ +settings->doctrine['devMode']); + + $config->setMetadataDriverImpl( + new AttributeDriver( + $this->settings->doctrine['metadataDirs'] + ) + ); + + return EntityManager::create( + $this->settings->connection, + $config, + ); + } +} diff --git a/implementation/16-data-repository/src/Factory/FileSystemSettingsProvider.php b/implementation/16-data-repository/src/Factory/FileSystemSettingsProvider.php new file mode 100644 index 0000000..f071078 --- /dev/null +++ b/implementation/16-data-repository/src/Factory/FileSystemSettingsProvider.php @@ -0,0 +1,22 @@ +filePath; + assert($settings instanceof Settings); + return $settings; + } +} diff --git a/implementation/16-data-repository/src/Factory/PipelineProvider.php b/implementation/16-data-repository/src/Factory/PipelineProvider.php new file mode 100644 index 0000000..77738f8 --- /dev/null +++ b/implementation/16-data-repository/src/Factory/PipelineProvider.php @@ -0,0 +1,25 @@ +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} diff --git a/implementation/16-data-repository/src/Factory/RequestFactory.php b/implementation/16-data-repository/src/Factory/RequestFactory.php new file mode 100644 index 0000000..2b17abc --- /dev/null +++ b/implementation/16-data-repository/src/Factory/RequestFactory.php @@ -0,0 +1,11 @@ +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = $settings; + $builder->addDefinitions($dependencies); + // $builder->enableCompilation('/tmp'); + return $builder->build(); + } +} diff --git a/implementation/16-data-repository/src/Factory/SettingsProvider.php b/implementation/16-data-repository/src/Factory/SettingsProvider.php new file mode 100644 index 0000000..ce1c5f0 --- /dev/null +++ b/implementation/16-data-repository/src/Factory/SettingsProvider.php @@ -0,0 +1,10 @@ +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + + echo $response->getBody(); + } +} diff --git a/implementation/16-data-repository/src/Http/ContainerPipeline.php b/implementation/16-data-repository/src/Http/ContainerPipeline.php new file mode 100644 index 0000000..816cedd --- /dev/null +++ b/implementation/16-data-repository/src/Http/ContainerPipeline.php @@ -0,0 +1,82 @@ + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} diff --git a/implementation/16-data-repository/src/Http/Emitter.php b/implementation/16-data-repository/src/Http/Emitter.php new file mode 100644 index 0000000..ce4c035 --- /dev/null +++ b/implementation/16-data-repository/src/Http/Emitter.php @@ -0,0 +1,10 @@ +getAttribute($this->routeAttributeName, false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} diff --git a/implementation/16-data-repository/src/Http/Pipeline.php b/implementation/16-data-repository/src/Http/Pipeline.php new file mode 100644 index 0000000..1a9dcda --- /dev/null +++ b/implementation/16-data-repository/src/Http/Pipeline.php @@ -0,0 +1,11 @@ +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} diff --git a/implementation/16-data-repository/src/Http/RoutedRequestHandler.php b/implementation/16-data-repository/src/Http/RoutedRequestHandler.php new file mode 100644 index 0000000..a7407c9 --- /dev/null +++ b/implementation/16-data-repository/src/Http/RoutedRequestHandler.php @@ -0,0 +1,10 @@ +pipeline->dispatch($request); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} diff --git a/implementation/16-data-repository/src/Model/MarkdownPage.php b/implementation/16-data-repository/src/Model/MarkdownPage.php new file mode 100644 index 0000000..df244fd --- /dev/null +++ b/implementation/16-data-repository/src/Model/MarkdownPage.php @@ -0,0 +1,13 @@ +dataPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return array_map(function (string $filename) { + $content = file_get_contents($filename); + if ($content === false) { + throw new InternalServerError('cannot read pages'); + } + $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); + return new MarkdownPage( + (int) substr($idAndTitle, 0, 2), + substr($idAndTitle, 3), + $content + ); + }, $files); + } + + public function byName(string $name): MarkdownPage + { + $pages = array_values( + array_filter( + $this->all(), + fn (MarkdownPage $p) => $p->title === $name, + ) + ); + + if (count($pages) !== 1) { + throw new NotFound; + } + + return $pages[0]; + } +} diff --git a/implementation/16-data-repository/src/Repository/MarkdownPageRepo.php b/implementation/16-data-repository/src/Repository/MarkdownPageRepo.php new file mode 100644 index 0000000..0792d32 --- /dev/null +++ b/implementation/16-data-repository/src/Repository/MarkdownPageRepo.php @@ -0,0 +1,15 @@ +engine->render($template, $data); + } +} diff --git a/implementation/16-data-repository/src/Template/ParsedownParser.php b/implementation/16-data-repository/src/Template/ParsedownParser.php new file mode 100644 index 0000000..2ffd287 --- /dev/null +++ b/implementation/16-data-repository/src/Template/ParsedownParser.php @@ -0,0 +1,17 @@ +parser->parse($markdown); + } +} diff --git a/implementation/16-data-repository/src/Template/Renderer.php b/implementation/16-data-repository/src/Template/Renderer.php new file mode 100644 index 0000000..ff916ed --- /dev/null +++ b/implementation/16-data-repository/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data = []): string; +} diff --git a/implementation/16-data-repository/templates/hello.html b/implementation/16-data-repository/templates/hello.html new file mode 100644 index 0000000..15a4cd2 --- /dev/null +++ b/implementation/16-data-repository/templates/hello.html @@ -0,0 +1,6 @@ +{{> partials/head }} +
+

Hello {{name}}

+

The time is {{now}}

+
+{{> partials/foot }} diff --git a/implementation/16-data-repository/templates/page.html b/implementation/16-data-repository/templates/page.html new file mode 100644 index 0000000..c3c5284 --- /dev/null +++ b/implementation/16-data-repository/templates/page.html @@ -0,0 +1,5 @@ +{{> partials/head }} +
+ {{{content}}} +
+{{> partials/foot }} diff --git a/implementation/16-data-repository/templates/page/list.html b/implementation/16-data-repository/templates/page/list.html new file mode 100644 index 0000000..bf42348 --- /dev/null +++ b/implementation/16-data-repository/templates/page/list.html @@ -0,0 +1,19 @@ + + + + + Pages + + + +
+ +
+ + \ No newline at end of file diff --git a/implementation/16-data-repository/templates/page/show.html b/implementation/16-data-repository/templates/page/show.html new file mode 100644 index 0000000..abe295e --- /dev/null +++ b/implementation/16-data-repository/templates/page/show.html @@ -0,0 +1,17 @@ + + + + + {{title}} + + + + + + +
+ {{{content}}} +
+ + \ No newline at end of file diff --git a/implementation/16-data-repository/templates/pagelist.html b/implementation/16-data-repository/templates/pagelist.html new file mode 100644 index 0000000..538e2c4 --- /dev/null +++ b/implementation/16-data-repository/templates/pagelist.html @@ -0,0 +1,11 @@ +{{> partials/head }} +
+ +
+{{> partials/foot }} diff --git a/implementation/16-data-repository/templates/partials/foot.html b/implementation/16-data-repository/templates/partials/foot.html new file mode 100644 index 0000000..17c7245 --- /dev/null +++ b/implementation/16-data-repository/templates/partials/foot.html @@ -0,0 +1,3 @@ +
+ + \ No newline at end of file diff --git a/implementation/16-data-repository/templates/partials/head.html b/implementation/16-data-repository/templates/partials/head.html new file mode 100644 index 0000000..421d387 --- /dev/null +++ b/implementation/16-data-repository/templates/partials/head.html @@ -0,0 +1,11 @@ + + + + + No Framework: {{title}} + + + + + +
From 3dd9f5a1d98f3d0209369b353d24850d85dce2d1 Mon Sep 17 00:00:00 2001 From: lubiana Date: Wed, 6 Apr 2022 23:43:03 +0200 Subject: [PATCH 280/314] add perfomance chapters --- 15-adding-content.md | 4 +- 17-performance.md | 37 +- 18-caching.md | 252 ++ Vagrantfile | 8 +- app/composer.json | 5 +- app/composer.lock | 6 +- app/config/dependencies.php | 11 +- app/config/middlewares.php | 2 + app/data/pages/04-development-helpers.md | 4 +- app/data/pages/12-configuration.md | 1 - app/data/pages/13-refactoring.md | 4 - app/data/pages/14-middleware.md | 13 +- app/data/pages/15-adding-content.md | 253 ++ app/data/pages/16-data-repository.md | 265 ++ app/data/pages/17-performance.md | 43 + app/data/pages/18-caching.md | 252 ++ app/public/index.php | 2 +- app/src/Action/Page.php | 2 +- app/src/Factory/DoctrineEm.php | 32 - app/src/Middleware/Cache.php | 38 + app/src/Repository/CachedMarkdownPageRepo.php | 49 + app/src/Service/Cache/ApcuCache.php | 21 + app/src/Service/Cache/EasyCache.php | 9 + implementation/18-caching/.php-cs-fixer.php | 38 + implementation/18-caching/.phpcs.xml.dist | 9 + implementation/18-caching/cli-config.php | 13 + implementation/18-caching/composer.json | 57 + implementation/18-caching/composer.lock | 2440 +++++++++++++++++ .../18-caching/config/dependencies.php | 58 + .../18-caching/config/middlewares.php | 13 + implementation/18-caching/config/routes.php | 15 + implementation/18-caching/config/settings.php | 12 + .../data/pages/01-front-controller.md | 53 + .../18-caching/data/pages/02-composer.md | 75 + .../18-caching/data/pages/03-error-handler.md | 79 + .../data/pages/04-development-helpers.md | 260 ++ .../18-caching/data/pages/05-http.md | 124 + .../18-caching/data/pages/06-router.md | 101 + .../data/pages/07-dispatching-to-a-class.md | 137 + .../data/pages/08-inversion-of-control.md | 54 + .../data/pages/09-dependency-injector.md | 213 ++ .../18-caching/data/pages/10-invoker.md | 102 + .../18-caching/data/pages/11-templating.md | 236 ++ .../18-caching/data/pages/12-configuration.md | 200 ++ .../18-caching/data/pages/13-refactoring.md | 373 +++ .../18-caching/data/pages/14-middleware.md | 303 ++ .../data/pages/15-adding-content.md | 253 ++ .../data/pages/16-data-repository.md | 265 ++ .../18-caching/data/pages/17-performance.md | 43 + .../18-caching/data/pages/18-caching.md | 252 ++ .../18-caching/phpstan-baseline.neon | 7 + implementation/18-caching/phpstan.neon | 8 + .../18-caching/public/css/spectre-exp.min.css | 1 + .../public/css/spectre-icons.min.css | 1 + .../18-caching/public/css/spectre.min.css | 1 + implementation/18-caching/public/favicon.ico | Bin 0 -> 15086 bytes implementation/18-caching/public/index.php | 5 + implementation/18-caching/src/.gitkeep | 0 .../18-caching/src/Action/Hello.php | 31 + .../18-caching/src/Action/Other.php | 16 + implementation/18-caching/src/Action/Page.php | 60 + implementation/18-caching/src/Bootstrap.php | 40 + .../src/Exception/InternalServerError.php | 9 + .../src/Exception/MethodNotAllowed.php | 9 + .../18-caching/src/Exception/NotFound.php | 9 + .../src/Factory/ContainerProvider.php | 10 + .../src/Factory/DiactorosRequestFactory.php | 28 + .../Factory/FileSystemSettingsProvider.php | 22 + .../src/Factory/PipelineProvider.php | 25 + .../18-caching/src/Factory/RequestFactory.php | 11 + .../src/Factory/SettingsContainerProvider.php | 26 + .../src/Factory/SettingsProvider.php | 10 + .../18-caching/src/Http/BasicEmitter.php | 38 + .../18-caching/src/Http/ContainerPipeline.php | 82 + .../18-caching/src/Http/Emitter.php | 10 + .../src/Http/InvokerRoutedHandler.php | 34 + .../18-caching/src/Http/Pipeline.php | 11 + .../18-caching/src/Http/RouteMiddleware.php | 69 + .../src/Http/RoutedRequestHandler.php | 10 + implementation/18-caching/src/Kernel.php | 32 + .../18-caching/src/Middleware/Cache.php | 38 + .../18-caching/src/Model/MarkdownPage.php | 13 + .../src/Repository/CachedMarkdownPageRepo.php | 49 + .../Repository/FileSystemMarkdownPageRepo.php | 61 + .../src/Repository/MarkdownPageRepo.php | 15 + .../src/Service/Cache/ApcuCache.php | 21 + .../src/Service/Cache/EasyCache.php | 9 + .../18-caching/src/Service/Time/Now.php | 10 + .../src/Service/Time/SystemClockNow.php | 13 + implementation/18-caching/src/Settings.php | 16 + .../src/Template/MarkdownParser.php | 8 + .../src/Template/MustacheRenderer.php | 17 + .../src/Template/ParsedownParser.php | 17 + .../18-caching/src/Template/Renderer.php | 11 + .../18-caching/templates/hello.html | 6 + implementation/18-caching/templates/page.html | 5 + .../18-caching/templates/page/list.html | 19 + .../18-caching/templates/page/show.html | 17 + .../18-caching/templates/pagelist.html | 11 + .../18-caching/templates/partials/foot.html | 3 + .../18-caching/templates/partials/head.html | 11 + 101 files changed, 8014 insertions(+), 62 deletions(-) create mode 100644 18-caching.md create mode 100644 app/data/pages/15-adding-content.md create mode 100644 app/data/pages/16-data-repository.md create mode 100644 app/data/pages/17-performance.md create mode 100644 app/data/pages/18-caching.md delete mode 100644 app/src/Factory/DoctrineEm.php create mode 100644 app/src/Middleware/Cache.php create mode 100644 app/src/Repository/CachedMarkdownPageRepo.php create mode 100644 app/src/Service/Cache/ApcuCache.php create mode 100644 app/src/Service/Cache/EasyCache.php create mode 100644 implementation/18-caching/.php-cs-fixer.php create mode 100644 implementation/18-caching/.phpcs.xml.dist create mode 100644 implementation/18-caching/cli-config.php create mode 100644 implementation/18-caching/composer.json create mode 100644 implementation/18-caching/composer.lock create mode 100644 implementation/18-caching/config/dependencies.php create mode 100644 implementation/18-caching/config/middlewares.php create mode 100644 implementation/18-caching/config/routes.php create mode 100644 implementation/18-caching/config/settings.php create mode 100644 implementation/18-caching/data/pages/01-front-controller.md create mode 100644 implementation/18-caching/data/pages/02-composer.md create mode 100644 implementation/18-caching/data/pages/03-error-handler.md create mode 100644 implementation/18-caching/data/pages/04-development-helpers.md create mode 100644 implementation/18-caching/data/pages/05-http.md create mode 100644 implementation/18-caching/data/pages/06-router.md create mode 100644 implementation/18-caching/data/pages/07-dispatching-to-a-class.md create mode 100644 implementation/18-caching/data/pages/08-inversion-of-control.md create mode 100644 implementation/18-caching/data/pages/09-dependency-injector.md create mode 100644 implementation/18-caching/data/pages/10-invoker.md create mode 100644 implementation/18-caching/data/pages/11-templating.md create mode 100644 implementation/18-caching/data/pages/12-configuration.md create mode 100644 implementation/18-caching/data/pages/13-refactoring.md create mode 100644 implementation/18-caching/data/pages/14-middleware.md create mode 100644 implementation/18-caching/data/pages/15-adding-content.md create mode 100644 implementation/18-caching/data/pages/16-data-repository.md create mode 100644 implementation/18-caching/data/pages/17-performance.md create mode 100644 implementation/18-caching/data/pages/18-caching.md create mode 100644 implementation/18-caching/phpstan-baseline.neon create mode 100644 implementation/18-caching/phpstan.neon create mode 100644 implementation/18-caching/public/css/spectre-exp.min.css create mode 100644 implementation/18-caching/public/css/spectre-icons.min.css create mode 100644 implementation/18-caching/public/css/spectre.min.css create mode 100644 implementation/18-caching/public/favicon.ico create mode 100644 implementation/18-caching/public/index.php create mode 100644 implementation/18-caching/src/.gitkeep create mode 100644 implementation/18-caching/src/Action/Hello.php create mode 100644 implementation/18-caching/src/Action/Other.php create mode 100644 implementation/18-caching/src/Action/Page.php create mode 100644 implementation/18-caching/src/Bootstrap.php create mode 100644 implementation/18-caching/src/Exception/InternalServerError.php create mode 100644 implementation/18-caching/src/Exception/MethodNotAllowed.php create mode 100644 implementation/18-caching/src/Exception/NotFound.php create mode 100644 implementation/18-caching/src/Factory/ContainerProvider.php create mode 100644 implementation/18-caching/src/Factory/DiactorosRequestFactory.php create mode 100644 implementation/18-caching/src/Factory/FileSystemSettingsProvider.php create mode 100644 implementation/18-caching/src/Factory/PipelineProvider.php create mode 100644 implementation/18-caching/src/Factory/RequestFactory.php create mode 100644 implementation/18-caching/src/Factory/SettingsContainerProvider.php create mode 100644 implementation/18-caching/src/Factory/SettingsProvider.php create mode 100644 implementation/18-caching/src/Http/BasicEmitter.php create mode 100644 implementation/18-caching/src/Http/ContainerPipeline.php create mode 100644 implementation/18-caching/src/Http/Emitter.php create mode 100644 implementation/18-caching/src/Http/InvokerRoutedHandler.php create mode 100644 implementation/18-caching/src/Http/Pipeline.php create mode 100644 implementation/18-caching/src/Http/RouteMiddleware.php create mode 100644 implementation/18-caching/src/Http/RoutedRequestHandler.php create mode 100644 implementation/18-caching/src/Kernel.php create mode 100644 implementation/18-caching/src/Middleware/Cache.php create mode 100644 implementation/18-caching/src/Model/MarkdownPage.php create mode 100644 implementation/18-caching/src/Repository/CachedMarkdownPageRepo.php create mode 100644 implementation/18-caching/src/Repository/FileSystemMarkdownPageRepo.php create mode 100644 implementation/18-caching/src/Repository/MarkdownPageRepo.php create mode 100644 implementation/18-caching/src/Service/Cache/ApcuCache.php create mode 100644 implementation/18-caching/src/Service/Cache/EasyCache.php create mode 100644 implementation/18-caching/src/Service/Time/Now.php create mode 100644 implementation/18-caching/src/Service/Time/SystemClockNow.php create mode 100644 implementation/18-caching/src/Settings.php create mode 100644 implementation/18-caching/src/Template/MarkdownParser.php create mode 100644 implementation/18-caching/src/Template/MustacheRenderer.php create mode 100644 implementation/18-caching/src/Template/ParsedownParser.php create mode 100644 implementation/18-caching/src/Template/Renderer.php create mode 100644 implementation/18-caching/templates/hello.html create mode 100644 implementation/18-caching/templates/page.html create mode 100644 implementation/18-caching/templates/page/list.html create mode 100644 implementation/18-caching/templates/page/show.html create mode 100644 implementation/18-caching/templates/pagelist.html create mode 100644 implementation/18-caching/templates/partials/foot.html create mode 100644 implementation/18-caching/templates/partials/head.html diff --git a/15-adding-content.md b/15-adding-content.md index c894d66..64562fa 100644 --- a/15-adding-content.md +++ b/15-adding-content.md @@ -1,4 +1,4 @@ -[<< previous](14-middleware.md) | [next >>](14-invoker.md) +[<< previous](14-middleware.md) | [next >>](16-data-repository.md) ### Adding Content @@ -250,4 +250,4 @@ add even more lines to that simple class, so lets move on to the next chapter wh classes following our holy SOLID principles :) -[<< previous](14-middleware.md) | [next >>](14-invoker.md) +[<< previous](14-middleware.md) | [next >>](16-data-repository.md) diff --git a/17-performance.md b/17-performance.md index d457bff..c83c7d5 100644 --- a/17-performance.md +++ b/17-performance.md @@ -1,6 +1,6 @@ -[<< previous](15-adding-content.md) | [next >>](17-performance.md) +[<< previous](16-data-repository.md) | [next >>](18-caching.md) -## Performance +## Autoloading performance Although our application is still very small and you should not really experience any performance issues right now, there are still some things we can already consider and take a look at. If I check the network tab in my browser it takes @@ -10,11 +10,34 @@ a template, some config files here and there and parse some markdown. So that sh The problem is, that we heavily rely on autoloading for all our class files, in the `src` folder. And there are also quite a lot of other files in composers `vendor` directory. To understand while this is becomming we should make -ourselves familiar with how autoloading in PHP works. +ourselves familiar with how [autoloading in php](https://www.php.net/manual/en/language.oop5.autoload.php) works. -[autoloading in php](https://www.php.net/manual/en/language.oop5.autoload.php) -[composer autoloader optimization](https://getcomposer.org/doc/articles/autoloader-optimization.md) +The basic idea is, that every class that php encounters has to be loaded from somewhere in the filesystem, we could +just require the files manually but that is tedious, unflexible and can often cause errors. -### Composer autoloading +The problem we are now facing is that the composer autoloader has some rules to determine from where in the filesystem +a class definition might be placed, then the autoloader tries to locate a file by the namespace and classname and if it +exists includes that file. -[<< previous](15-adding-content.md) | [next >>](17-performance.md) +If we only have a handfull of classes that does not take a lot of time, but as we are growing with our application this +easily takes longer than necesery, but fortunately composer has some options to speed up the class loading. + +Take a few minutes to read the documentation about [composer autoloader optimization](https://getcomposer.org/doc/articles/autoloader-optimization.md) + +You can try all 3 levels of optimizations, but we are going to stick with the first one for now, so lets create an +optimized classmap. + +`composer dump-autoload -o` + +After composer has finished you can start the devserver again with `composer serve` and take a look at the network tab +in your browsers devtools. + +In my case the response time falls down to under an average of 30ms with some spikes in between, but all in all it looks really good. +You can also try out the different optimization levels and see if you can spot any differences. + +Although the composer manual states not to use the optimtization in a dev environment I personally have not encountered +any errors with the first level of optimizations, so we can use that level here. If you add the line from the documentation +to your `composer.json` so that the autoloader gets optimized everytime we install new packages. + + +[<< previous](16-data-repository.md) | [next >>](18-caching.md) diff --git a/18-caching.md b/18-caching.md new file mode 100644 index 0000000..f06abe9 --- /dev/null +++ b/18-caching.md @@ -0,0 +1,252 @@ +[<< previous](17-performance.md) | [next >>](19-database.md) + +**DISClAIMER** I do not really have a lot of experience when it comes to caching, so this chapter is mostly some random +thoughts and ideas I wanted to explore when writing this tutorial, you should definitely take everything that is being +said here with caution and try to read up on some other sources. But that holds true for the whole tutorial anyway :) + +## Caching + +In the last chapter we greatly improved the perfomance for the lookup of all our classfiles, but currently we do not +have any real bottlenecks in our application like complex queries. + +But in a real application we are going to execute some really heavy and time intensive database queries that can take +quite a while to be completed. + +We can simulate that by adding a simple delay in our `FileSystemMarkdownPageRepo`. + +```php + return array_map(function (string $filename) { + usleep(rand(100, 400) * 1000); + $content = file_get_contents($filename); + if ($content === false) { + throw new InternalServerError('cannot read pages'); + } + $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); + return new MarkdownPage( + (int) substr($idAndTitle, 0, 2), + substr($idAndTitle, 3), + $content + ); +}); +``` + +Here I added a function that pauses the scripts execution for a random time between 100 and 400ms for every markdownpage +in every call of the `all()` method. + +If you open any page or even the listAction in you browser you will see, that it takes quite a time to render that page. +Although this is a silly example we do not really need to query the database on every request, so lets add a way to cache +the database results between requests. + +The PHP-Community has already adressed the issue of having easy to use access to cache libraries, there is the +[PSR-6 Caching Interface](https://www.php-fig.org/psr/psr-6) which gives us easy access to many different implementations, +then there is also a much simpler [PSR-16 Simple Cache](https://www.php-fig.org/psr/psr-16) which makes the use even more +easy, and most Caching Libraries implement Both interfaces anyway. You would think that this is more than enough solutions +to satisfy all the Caching needs around, but the Symfony People decided that Caching should be even simpler and easier +to use and defined their own [Interface](https://symfony.com/doc/current/components/cache.html#cache-component-contracts) +which only needs two methods. You should definitely take a look at the linked documentation as it really blew my mind +when I first encountered it. + +The basic idea is that you provide a callback that computes the requested value. The Cache implementation then checks +if it already has the value stored somewhere and if it doesnt it just executes the callback and stores the value for +future calls. + +It is really simple and great to use. In a real world application you should definitely use that or a PSR-16 implementation +but for this tutorial I wanted to roll out my own solution, so here we go. + +As always we are going to define an interface first, I am going to call it EasyCache and place it in the `Service/Cache` +namespace. I will require only one method which is base on the Symfony Cache Contract, and hast a key, a callback, and +the duration that the item should be cached as arguments. + +```php +cache->get( + $key, + fn () => $this->repo->all(), + 300 + ); + } + + public function byName(string $name): MarkdownPage + { + $key = base64_encode(self::class . 'byName' . $name); + return $this->cache->get( + $key, + fn () => $this->repo->byName($name), + 300 + ); + } +} +``` + +This simple wrapper just requires an EasyCache implementation and a MarkdownPageRepo in the constructor and uses them +to cache all queries for 5 minutes. The beauty is that we are not dependent on any implementation here, so we can switch +out the Repository or the Cache at any point down the road if we want to. + +In order to use that we need to update our `config/dependencies.php` to add an alias for the EasyCache interface as well +as defining our CachedMarkdownPageRepo as implementation for the MarkdownPageRepo interface: + +```php +MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, +EasyCache::class => fn (ApcuCache $c) => $c, +``` + +If we try to access our webpage now, we are getting an error, as PHP-DI has detected a circular dependency that cannot +be autowired. + +The Problem is that our CachedMarkdownPageRepo ist defined as the implementation for the MarkdownPageRepo, but it also +requires that exact interface as a dependency. To resolve this issue we need to manually tell the container how to build +the CachedMarkdownPageRepo by adding another line to the `config/dependencies.php` file: + +```php +CachedMarkdownPageRepo::class => fn (EasyCache $c, FileSystemMarkdownPageRepo $r) => new CachedMarkdownPageRepo($c, $r), +``` + +Here we explicitly require the FileSystemMarkdownPageRepo and us that to create the CachedMarkdownPageRepo object. + +When you now navigate to the pages list or to a specific page the first load should take a while (because of our added delay) +but the following request should be answered blazingly fast. + +Before moving on to the next chapter we can take the caching approach even further, in the middleware chapter I talked +about a simple CachingMiddleware that caches all the GET-Request for some seconds, as they should not change that often, +and we can bypass most of our application logic if we just complelety cache away the responses our application generates, +and return them quite early in our Middleware-Pipeline befor the router gets called, or the invoker calls the action, +which itself uses some other services to fetch all the needed data. + +We will introduce a new `Middleware` namespace to place our `Cache.php` middleware: +```php +getMethod() !== 'GET') { + return $handler->handle($request); + } + $keyHash = base64_encode($request->getUri()->getPath()); + $result = $this->cache->get( + $keyHash, + fn () => $this->serializer::toString($handler->handle($request)), + 300 + ); + return $this->serializer::fromString($result); + } +} +``` + +The code is quite straight forward, but you might be confused by the Responseserializer I have added here, we need this +because the response body is a stream object, which doesnt always gets serialized correctly, therefore I use a class from +the laminas project to to all the heavy lifting for us. + +We need to add the now middleware to the `config/middlewares.php` file. + +```php +>](19-database.md) diff --git a/Vagrantfile b/Vagrantfile index 56a4db4..7cdc936 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -3,9 +3,13 @@ Vagrant.configure("2") do |config| config.vm.box = "archlinux/archlinux" + config.vm.provider "virtualbox" do |v| + v.memory = 2048 + v.cpus = 4 + end config.vm.network "forwarded_port", guest: 1234, host: 1234 config.vm.network "forwarded_port", guest: 22, host: 2200, id: 'ssh' - config.vm.synced_folder "./app", "/home/vagrant/app" + config.vm.synced_folder "./app", "/home/vagrant/app/" config.ssh.username = 'vagrant' config.ssh.password = 'vagrant' config.vm.provision "shell", inline: <<-SHELL @@ -17,5 +21,7 @@ Vagrant.configure("2") do |config| echo -e 'zend_extension=xdebug\nxdebug.client_host=10.0.2.2\n' >> /etc/php/conf.d/tutorial.ini echo -e 'xdebug.client_port=9003\nxdebug.mode=debug\n' >> /etc/php/conf.d/tutorial.ini echo -e 'zend.assertions=1\n' >> /etc/php/conf.d/tutorial.ini + echo -e 'opcache.enable=1\nopcache.enable_cli=1\n' >> /etc/php/conf.d/tutorial.ini + echo -e 'acp.enable=1\napc.enable_cli=1\n' >> /etc/php/conf.d/tutorial.ini SHELL end diff --git a/app/composer.json b/app/composer.json index b5c7f1a..29695da 100644 --- a/app/composer.json +++ b/app/composer.json @@ -12,7 +12,9 @@ "middlewares/trailing-slash": "^2.0", "middlewares/whoops": "^2.0", "erusev/parsedown": "^1.7", - "league/commonmark": "^2.2" + "league/commonmark": "^2.2", + "ext-apcu": "*", + "ext-zend-opcache": "*" }, "autoload": { "psr-4": { @@ -36,6 +38,7 @@ "mnapoli/hard-mode": "^0.3.0" }, "config": { + "optimize-autoloader": true, "allow-plugins": { "phpstan/extension-installer": true, "dealerdirect/phpcodesniffer-composer-installer": true diff --git a/app/composer.lock b/app/composer.lock index a62d9c7..40cd7d3 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "00acf07ae222f9117a84bce157b99837", + "content-hash": "9a29468fd456190a9fbcff98ed42d862", "packages": [ { "name": "dflydev/dot-access-data", @@ -2431,7 +2431,9 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^8.1" + "php": "^8.1", + "ext-apcu": "*", + "ext-zend-opcache": "*" }, "platform-dev": [], "plugin-api-version": "2.3.0" diff --git a/app/config/dependencies.php b/app/config/dependencies.php index 0040933..df815c6 100644 --- a/app/config/dependencies.php +++ b/app/config/dependencies.php @@ -11,8 +11,11 @@ use Lubian\NoFramework\Http\InvokerRoutedHandler; use Lubian\NoFramework\Http\Pipeline; use Lubian\NoFramework\Http\RoutedRequestHandler; use Lubian\NoFramework\Http\RouteMiddleware; +use Lubian\NoFramework\Repository\CachedMarkdownPageRepo; use Lubian\NoFramework\Repository\FileSystemMarkdownPageRepo; use Lubian\NoFramework\Repository\MarkdownPageRepo; +use Lubian\NoFramework\Service\Cache\ApcuCache; +use Lubian\NoFramework\Service\Cache\EasyCache; use Lubian\NoFramework\Service\Time\Now; use Lubian\NoFramework\Service\Time\SystemClockNow; use Lubian\NoFramework\Settings; @@ -26,8 +29,6 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; -use Symfony\Component\Cache\Adapter\FilesystemAdapter; -use Symfony\Contracts\Cache\CacheInterface; use function FastRoute\simpleDispatcher; @@ -39,9 +40,11 @@ return [ MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, - CacheInterface::class => fn (FilesystemAdapter $a) => $a, MarkdownParser::class => fn (ParsedownParser $p) => $p, - MarkdownPageRepo::class => fn (FileSystemMarkdownPageRepo $r) => $r, + MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, + EasyCache::class => fn (ApcuCache $c) => $c, + CachedMarkdownPageRepo::class => fn (EasyCache $c, FileSystemMarkdownPageRepo $r) => new CachedMarkdownPageRepo($c, $r), + // Factories ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), diff --git a/app/config/middlewares.php b/app/config/middlewares.php index 71dd461..ab662be 100644 --- a/app/config/middlewares.php +++ b/app/config/middlewares.php @@ -1,11 +1,13 @@ >](14-invoker.md) +[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) ### Middleware @@ -153,8 +153,6 @@ class ContainerPipeline implements Pipeline { /** * @param array $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container */ public function __construct( private array $middlewares, @@ -295,4 +293,11 @@ Lets try if you can make the kernel work with our created Pipeline implementatio pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a dependency container, if you want you can do that as well. -[<< previous](12-refactoring.md) | [next >>](14-invoker.md) +**A quick note about docblocks:** You might have noticed, that I rarely add docblocks to my the code in the examples, and +when I do it seems kind of random. My philosophy is that I only add docblocks when there is no way to automatically get +the exact type from the code itself. For me docblocks only serve two purposes: help my IDE to understand what it choices +it has for code completion and to help the static analysis to better understand the code. There is a great blogpost +about the [cost and value of DocBlocks](https://localheinz.com/blog/2018/05/06/cost-and-value-of-docblocks/), although it +is written in 2018 at a time before PHP 7.4 was around everything written there is still valid today. + +[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) diff --git a/app/data/pages/15-adding-content.md b/app/data/pages/15-adding-content.md new file mode 100644 index 0000000..64562fa --- /dev/null +++ b/app/data/pages/15-adding-content.md @@ -0,0 +1,253 @@ +[<< previous](14-middleware.md) | [next >>](16-data-repository.md) + +### Adding Content + +By now we did not really display anything but some examples to in our application and it is now time to make our app +display some content. For example we could our app be able to display the Markdown files used in this tutorial as +nicely rendered HTML Pages that can be viewed in the browser instead of the editor you are using. + +So lets start by copying the markdown files to our app directory. I have created a new folder 'data/pages' and placed all +the markdown files in there. + +Next we need a markdown parser, a pretty simple one is [Parsedown](https://parsedown.org/), if you want more features +you could also use the [Commonmark parser](https://commonmark.thephpleague.com/). I will choose Parsedown here, but you +can use whatever you like. + +After installing Parsedown lets write a Markdownparser interface and an implementation using parsedown. + +We only need one function that receives a string of Markdown and returns the HTML represantion (as a string as well). + + + +```php +parser->parse($markdown); + } +} +``` + +We could now use the ParsedownRender class directly in our actions by typehinting the classname as an argument to the +constructor or a method, but as we always want to rely on an interface instead of an implementation we need to define +the the ParsedownRenderer as the correct implementation for the MarkdownRenderer interface in the dependencies file: + +```php +... + \Lubian\NoFramework\Template\MarkdownParser::class => fn(\Lubian\NoFramework\Template\ParsedownParser $p) => $p, +... +``` + +You can test that in our "Other.php" action and try out if the Parser works and is able to render Markdown to HTML: + +```php +public function someFunctionName(ResponseInterface $response, MarkdownParser $parser): ResponseInterface +{ + $html = $parser->parse('This *works* **too!**'); + $response->getBody()->write($html); + return $response->withStatus(200); +} +``` + +But we want to display complete Pages written in Markdown, it would also be neat to be able to display a list of all +available pages. For that we need a few things: + +Firstly we need two new Templates, one for the list of the Pages, and the second one for displaying a single pages +content. Create a new folder in `templates/page` with to files: + +`templates/page/list.html` +```html + + + + + Pages + + + +
+ +
+ + +``` + +This template iterates over a provided array of pages, each element consists of the two properties: an id and a title, +those are simply displayed using an unordered list. + +`templates/page/show.html` +```html + + + + + {{title}} + + + + + + +
+ {{{content}}} +
+ + +``` + +The second templates displays a single rendered markdown page. As data it expects the title and the content as array. +I used an extra bracket for the content ```{{{content}}}``` so that the Mustache-Renderer does not escape the provided +html and thereby destroys the the parsed markdown. + +You might have spotted that I added [Pico.css](https://picocss.com/) which is just a very small css framework to make the +pages a little bit nicer to look at. It mostly provides some typography styles that work great with rendered Markdown, +but you can leave that out or use any other css framework you like. There is also some Javascript that adds syntax +highlighting to the code. + +After you have taken care of the templating side we can now create an new Action class with two methods to display use +our markdown files and the templates to create the pages. As we have two templates I propose to use Two methods in our +Action: +`src/Action/Page.php` +```php +function show(string $name): \Psr\Http\Message\ResponseInterface; +function list(): \Psr\Http\Message\ResponseInterface; +``` + +Lets define two routes. `/page` should display the overview of all pages, and if the add the name of chapter to the +route, `/page/adding-content` for example, the show action should be called with the name as a variable: + +`config/routes.php` +```php +$r->addRoute('GET', '/page', [Page::class, 'list']); +$r->addRoute('GET', '/page/{page}', [Page::class, 'show']); +``` + +Here is my Implementation. If have added a little regex replacement in the show method that replaces the links to the +next and previous chapter so that it works with our routing configuration. + +`src/Action/Page.php` +```php +getPages(), + fn (string $filename) => str_contains($filename, $page) + ) + )[0]; + $markdown = file_get_contents($page); + + // fix the next and previous buttons to work with our routing + $markdown = preg_replace('/\(\d\d-/m', '(', $markdown); + $markdown = str_replace('.md)', ')', $markdown); + + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + $data = [ + 'title' => substr($page, 3), + 'content' => $this->parser->parse($markdown), + ]; + $html = $this->renderer->render('page/show', $data); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map(function (string $page) { + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + return [ + 'id' => substr($page, 0, 2), + 'title' => substr($page, 3), + ]; + }, $this->getPages()); + $html = $this->renderer->render('page/list', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } + + /** + * @return string[] + */ + private function getPages(): array + { + $files = glob($this->pagesPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return $files; + } +} +``` + +You can now navigate your Browser to [localhost:1234/page][http://localhost:1234/page] and try out if everything works. + +Of course this code is far from looking good. We heavily rely on the pages being files in the filesystem, and the action +should never be aware of the filesystem in the first place, also we have a lot of string replacements and other repetetive +code in the file. And phpstan is gonna scream at us a lot, but if we rewrite the code to satisfy all the checks we would +add even more lines to that simple class, so lets move on to the next chapter where we move all the logic to seperate +classes following our holy SOLID principles :) + + +[<< previous](14-middleware.md) | [next >>](16-data-repository.md) diff --git a/app/data/pages/16-data-repository.md b/app/data/pages/16-data-repository.md new file mode 100644 index 0000000..d9a3218 --- /dev/null +++ b/app/data/pages/16-data-repository.md @@ -0,0 +1,265 @@ +[<< previous](15-adding-content.md) | [next >>](17-performance.md) + +## Data Repository + +At the end of the last chapter I mentioned being unhappy with our Pages action, because there is to much stuff happening +there. We are firstly receiving some Arguments, then we are using those to query the filesytem for the given page, +loading the specific file from the filesystem, rendering the markdown, passing the markdown to the template renderer, +adding the resulting html to the response and then returning the response. + +In order to make our pageaction independent from the filesystem and move the code that is responsible for reading the +files +to a better place I want to introduce +the [Repository Pattern](https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html). + +I want to start by creating a class that represents the Data that is included in a page so that. For now I can spot +three +distrinct attributes. + +* the ID (or chapternumber) +* the title (or name) +* the content + +Currently all those properties are always available, but we might later be able to create new pages and store them, but +at that point in time we are not yet aware of the new available ID, so we should leave that property nullable. This +allows +us to create an object without an id and let the code that actually saves the object to a persistant store define a +valid +id on saving. + +Lets create an new Namespace called `Model` and put a `MarkdownPage.php` class in there: + +```php +dataPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return array_map(function (string $filename) { + $content = file_get_contents($filename); + if ($content === false) { + throw new InternalServerError('cannot read pages'); + } + $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); + return new MarkdownPage( + (int) substr($idAndTitle, 0, 2), + substr($idAndTitle, 3), + $content + ); + }, $files); + } + + public function byName(string $name): MarkdownPage + { + $pages = array_values( + array_filter( + $this->all(), + fn (MarkdownPage $p) => $p->title === $name, + ) + ); + + if (count($pages) !== 1) { + throw new NotFound; + } + + return $pages[0]; + } +} +``` + +With that in place we need to add the required `$pagesPath` to our settings class and add specify that in our +configuration. + +`src/Settings.php` + +```php +final class Settings +{ + public function __construct( + public readonly string $environment, + public readonly string $dependenciesFile, + public readonly string $middlewaresFile, + public readonly string $templateDir, + public readonly string $templateExtension, + public readonly string $pagesPath, + ) { + } +} +``` + +`config/settings.php` + +```php +return new Settings( + environment: 'prod', + dependenciesFile: __DIR__ . '/dependencies.php', + middlewaresFile: __DIR__ . '/middlewares.php', + templateDir: __DIR__ . '/../templates', + templateExtension: '.html', + pagesPath: __DIR__ . '/../data/pages/', +); +``` + +Of course we need to define the correct implementation for the container to choose when we are requesting the Repository +interface: +`conf/dependencies.php` + +```php +MarkdownPageRepo::class => fn (FileSystemMarkdownPageRepo $r) => $r, +FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), +``` + +Now you can request the MarkdownPageRepo Interface in your page action and use the defined functions to get the +MarkdownPage +Objects. My `src/Action/Page.php` looks like this now: + +```php +repo->byName($page); + + // fix the next and previous buttons to work with our routing + $content = preg_replace('/\(\d\d-/m', '(', $page->content); + assert(is_string($content)); + $content = str_replace('.md)', ')', $content); + + $data = [ + 'title' => $page->title, + 'content' => $this->parser->parse($content), + ]; + + $html = $this->renderer->render('page/show', $data); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map(function (MarkdownPage $page) { + return [ + 'id' => $page->id, + 'title' => $page->content, + ]; + }, $this->repo->all()); + + $html = $this->renderer->render('page/list', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } +} +``` + +Check the page in your browser if everything still works, don't forget to run phpstan and the others fixers before +committing your changes and moving on to the next chapter. + +[<< previous](15-adding-content.md) | [next >>](17-performance.md) diff --git a/app/data/pages/17-performance.md b/app/data/pages/17-performance.md new file mode 100644 index 0000000..c83c7d5 --- /dev/null +++ b/app/data/pages/17-performance.md @@ -0,0 +1,43 @@ +[<< previous](16-data-repository.md) | [next >>](18-caching.md) + +## Autoloading performance + +Although our application is still very small and you should not really experience any performance issues right now, +there are still some things we can already consider and take a look at. If I check the network tab in my browser it takes +about 90-400ms to show a simple rendered markdownpage, with is sort of ok but in my opinion way to long as we are not +really doing anything and do not connect to any external services. Mostly we are just reading around 16 markdown files, +a template, some config files here and there and parse some markdown. So that should not really take that long. + +The problem is, that we heavily rely on autoloading for all our class files, in the `src` folder. And there are also +quite a lot of other files in composers `vendor` directory. To understand while this is becomming we should make +ourselves familiar with how [autoloading in php](https://www.php.net/manual/en/language.oop5.autoload.php) works. + +The basic idea is, that every class that php encounters has to be loaded from somewhere in the filesystem, we could +just require the files manually but that is tedious, unflexible and can often cause errors. + +The problem we are now facing is that the composer autoloader has some rules to determine from where in the filesystem +a class definition might be placed, then the autoloader tries to locate a file by the namespace and classname and if it +exists includes that file. + +If we only have a handfull of classes that does not take a lot of time, but as we are growing with our application this +easily takes longer than necesery, but fortunately composer has some options to speed up the class loading. + +Take a few minutes to read the documentation about [composer autoloader optimization](https://getcomposer.org/doc/articles/autoloader-optimization.md) + +You can try all 3 levels of optimizations, but we are going to stick with the first one for now, so lets create an +optimized classmap. + +`composer dump-autoload -o` + +After composer has finished you can start the devserver again with `composer serve` and take a look at the network tab +in your browsers devtools. + +In my case the response time falls down to under an average of 30ms with some spikes in between, but all in all it looks really good. +You can also try out the different optimization levels and see if you can spot any differences. + +Although the composer manual states not to use the optimtization in a dev environment I personally have not encountered +any errors with the first level of optimizations, so we can use that level here. If you add the line from the documentation +to your `composer.json` so that the autoloader gets optimized everytime we install new packages. + + +[<< previous](16-data-repository.md) | [next >>](18-caching.md) diff --git a/app/data/pages/18-caching.md b/app/data/pages/18-caching.md new file mode 100644 index 0000000..42e9cb1 --- /dev/null +++ b/app/data/pages/18-caching.md @@ -0,0 +1,252 @@ +[<< previous](17-performance.md) | [next >>](19-database.md) + +**DISClAIMER** I do not really have a lot of experience when it comes to caching, so this chapter is mostly some random +thoughts and ideas I wanted to explore when writing this tutorial, you should definitely take everything that is being +said here with caution and try to read up on some other sources. But that holds true for the whole tutorial anyway :) + +## Caching + +In the last chapter we greatly improved the perfomance for the lookup of all our classfiles, but currently we do not +have any real bottlenecks in our application like complex queries. + +But in a real application we are going to execute some really heavy and time intensive database queries that can take +quite a while to be completed. + +We can simulate that by adding a simple delay in our `FileSystemMarkdownPageRepo`. + +```php + return array_map(function (string $filename) { + usleep(rand(100, 400) * 1000); + $content = file_get_contents($filename); + if ($content === false) { + throw new InternalServerError('cannot read pages'); + } + $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); + return new MarkdownPage( + (int) substr($idAndTitle, 0, 2), + substr($idAndTitle, 3), + $content + ); +}); +``` + +Here I added a function that pauses the scripts execution for a random time between 100 and 400ms for every markdownpage +in every call of the `all()` method. + +If you open any page or even the listAction in you browser you will see, that it takes quite a time to render that page. +Although this is a silly example we do not really need to query the database on every request, so lets add a way to cache +the database results between requests. + +The PHP-Community has already adressed the issue of having easy to use access to cache libraries, there is the +[PSR-6 Caching Interface](https://www.php-fig.org/psr/psr-6) which gives us easy access to many different implementations, +then there is also a much simpler [PSR-16 Simple Cache](https://www.php-fig.org/psr/psr-16) which makes the use even more +easy, and most Caching Libraries implement Both interfaces anyway. You would think that this is more than enough solutions +to satisfy all the Caching needs around, but the Symfony People decided that Caching should be even simpler and easier +to use and defined their own [Interface](https://symfony.com/doc/current/components/cache.html#cache-component-contracts) +which only needs two methods. You should definitely take a look at the linked documentation as it really blew my mind +when I first encountered it. + +The basic idea is that you provide a callback that computes the requested value. The Cache implementation then checks +if it already has the value stored somewhere and if it doesnt it just executes the callback and stores the value for +future calls. + +It is really simple and great to use. In a real world application you should definitely use that or a PSR-16 implementation +but for this tutorial I wanted to roll out my own solution, so here we go. + +As always we are going to define an interface first, I am going to call it EasyCache and place it in the `Service/Cache` +namespace. I will require only one method which is base on the Symfony Cache Contract, and hast a key, a callback, and +the duration that the item should be cached as arguments. + +```php +cache->get( + $key, + fn () => $this->repo->all(), + 300 + ); + } + + public function byName(string $name): MarkdownPage + { + $key = base64_encode(self::class . 'byName' . $name); + return $this->cache->get( + $key, + fn () => $this->repo->byName($name), + 300 + ); + } +} +``` + +This simple wrapper just requires an EasyCache implementation and a MarkdownPageRepo in the constructor and uses them +to cache all queries for 5 minutes. The beauty is that we are not dependent on any implementation here, so we can switch +out the Repository or the Cache at any point down the road if we want to. + +In order to use that we need to update our `config/dependencies.php` to add an alias for the EasyCache interface as well +as defining our CachedMarkdownPageRepo as implementation for the MarkdownPageRepo interface: + +```php +MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, +EasyCache::class => fn (ApcuCache $c) => $c, +``` + +If we try to access our webpage now, we are getting an error, as PHP-DI has detected a circular dependency that cannot +be autowired. + +The Problem is that our CachedMarkdownPageRepo ist defined as the implementation for the MarkdownPageRepo, but it also +requires that exact interface as a dependency. To resolve this issue we need to manually tell the container how to build +the CachedMarkdownPageRepo by adding another line to the `config/dependencies.php` file: + +```php +CachedMarkdownPageRepo::class => fn (EasyCache $c, FileSystemMarkdownPageRepo $r) => new CachedMarkdownPageRepo($c, $r), +``` + +Here we explicitly require the FileSystemMarkdownPageRepo and us that to create the CachedMarkdownPageRepo object. + +When you now navigate to the pages list or to a specific page the first load should take a while (because of our added delay) +but the following request should be answered blazingly fast. + +Before moving on to the next chapter we can take the caching approach even further, in the middleware chapter I talked +about a simple CachingMiddleware that caches all the GET-Request for some seconds, as they should not change that often, +and we can bypass most of our application logic if we just complelety cache away the responses our application generates, +and return them quite early in our Middleware-Pipeline befor the router gets called, or the invoker calls the action, +which itself uses some other services to fetch all the needed data. + +We will introduce a new `Middleware` namespace to place our `Cache.php` middleware: +```php +getMethod() !== 'GET') { + return $handler->handle($request); + } + $keyHash = base64_encode($request->getUri()->getPath()); + $result = $this->cache->get( + $keyHash, + fn () => Serializer::toString($handler->handle($request)), + 300 + ); + return Serializer::fromString($result); + } +} +``` + +The code is quite straight forward, but you might be confused by the Responseserializer I have added here, we need this +because the response body is a stream object, which doesnt always gets serialized correctly, therefore I use a class from +the laminas project to to all the heavy lifting for us. + +We need to add the now middleware to the `config/middlewares.php` file. + +```php +>](19-database.md) diff --git a/app/public/index.php b/app/public/index.php index d93da3a..32f5eb3 100644 --- a/app/public/index.php +++ b/app/public/index.php @@ -2,4 +2,4 @@ declare(strict_types=1); -require __DIR__ . '/../src/Bootstrap.php'; \ No newline at end of file +require __DIR__ . '/../src/Bootstrap.php'; diff --git a/app/src/Action/Page.php b/app/src/Action/Page.php index 4af45f0..96696e4 100644 --- a/app/src/Action/Page.php +++ b/app/src/Action/Page.php @@ -49,7 +49,7 @@ class Page $pages = array_map(function (MarkdownPage $page) { return [ 'id' => $page->id, - 'title' => $page->content, + 'title' => $page->title, ]; }, $this->repo->all()); diff --git a/app/src/Factory/DoctrineEm.php b/app/src/Factory/DoctrineEm.php deleted file mode 100644 index b0be39b..0000000 --- a/app/src/Factory/DoctrineEm.php +++ /dev/null @@ -1,32 +0,0 @@ -settings->doctrine['devMode']); - - $config->setMetadataDriverImpl( - new AttributeDriver( - $this->settings->doctrine['metadataDirs'] - ) - ); - - return EntityManager::create( - $this->settings->connection, - $config, - ); - } -} diff --git a/app/src/Middleware/Cache.php b/app/src/Middleware/Cache.php new file mode 100644 index 0000000..8460761 --- /dev/null +++ b/app/src/Middleware/Cache.php @@ -0,0 +1,38 @@ +getMethod() !== 'GET') { + return $handler->handle($request); + } + $keyHash = base64_encode($request->getUri()->getPath()); + $result = $this->cache->get( + $keyHash, + fn () => $this->serializer::toString($handler->handle($request)), + 300 + ); + assert(is_string($result)); + return $this->serializer::fromString($result); + } +} diff --git a/app/src/Repository/CachedMarkdownPageRepo.php b/app/src/Repository/CachedMarkdownPageRepo.php new file mode 100644 index 0000000..a4d9180 --- /dev/null +++ b/app/src/Repository/CachedMarkdownPageRepo.php @@ -0,0 +1,49 @@ +cache->get( + $key, + fn () => $this->repo->all(), + 300 + ); + assert(is_array($result)); + foreach ($result as $page) { + assert($page instanceof MarkdownPage); + } + return $result; + } + + public function byName(string $name): MarkdownPage + { + $key = base64_encode(self::class . 'byName' . $name); + $result = $this->cache->get( + $key, + fn () => $this->repo->byName($name), + 300 + ); + assert($result instanceof MarkdownPage); + return $result; + } +} diff --git a/app/src/Service/Cache/ApcuCache.php b/app/src/Service/Cache/ApcuCache.php new file mode 100644 index 0000000..4835098 --- /dev/null +++ b/app/src/Service/Cache/ApcuCache.php @@ -0,0 +1,21 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/config' + ]) + ); \ No newline at end of file diff --git a/implementation/18-caching/.phpcs.xml.dist b/implementation/18-caching/.phpcs.xml.dist new file mode 100644 index 0000000..3b433f6 --- /dev/null +++ b/implementation/18-caching/.phpcs.xml.dist @@ -0,0 +1,9 @@ + + + + + src + config + + + \ No newline at end of file diff --git a/implementation/18-caching/cli-config.php b/implementation/18-caching/cli-config.php new file mode 100644 index 0000000..fbc6598 --- /dev/null +++ b/implementation/18-caching/cli-config.php @@ -0,0 +1,13 @@ +getContainer(); + +return ConsoleRunner::createHelperSet($container->get(EntityManagerInterface::class)); diff --git a/implementation/18-caching/composer.json b/implementation/18-caching/composer.json new file mode 100644 index 0000000..29695da --- /dev/null +++ b/implementation/18-caching/composer.json @@ -0,0 +1,57 @@ +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.8", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "php-di/php-di": "^6.3", + "mustache/mustache": "^2.14", + "psr/http-server-middleware": "^1.0", + "middlewares/trailing-slash": "^2.0", + "middlewares/whoops": "^2.0", + "erusev/parsedown": "^1.7", + "league/commonmark": "^2.2", + "ext-apcu": "*", + "ext-zend-opcache": "*" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubian", + "email": "test@example.com" + } + ], + "require-dev": { + "phpstan/phpstan": "^1.5", + "php-cs-fixer/shim": "^3.8", + "symfony/var-dumper": "^6.0", + "squizlabs/php_codesniffer": "^3.6", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.1", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "mnapoli/hard-mode": "^0.3.0" + }, + "config": { + "optimize-autoloader": true, + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1234 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + } +} diff --git a/implementation/18-caching/composer.lock b/implementation/18-caching/composer.lock new file mode 100644 index 0000000..40cd7d3 --- /dev/null +++ b/implementation/18-caching/composer.lock @@ -0,0 +1,2440 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "9a29468fd456190a9fbcff98ed42d862", + "packages": [ + { + "name": "dflydev/dot-access-data", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", + "reference": "0992cc19268b259a39e86f296da5f0677841f42c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^3.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" + }, + "time": "2021-08-13T13:06:58+00:00" + }, + { + "name": "erusev/parsedown", + "version": "1.7.4", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "support": { + "issues": "https://github.com/erusev/parsedown/issues", + "source": "https://github.com/erusev/parsedown/tree/1.7.x" + }, + "time": "2019-12-30T22:54:17+00:00" + }, + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", + "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-03-29T20:12:16+00:00" + }, + { + "name": "league/commonmark", + "version": "2.2.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", + "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.15" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.30.0", + "commonmark/commonmark.js": "0.30.0", + "composer/package-versions-deprecated": "^1.8", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4", + "phpstan/phpstan": "^0.12.88 || ^1.0.0", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2022-02-26T21:24:45+00:00" + }, + { + "name": "league/config", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.90", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2021-08-14T12:15:32+00:00" + }, + { + "name": "middlewares/trailing-slash", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/middlewares/trailing-slash.git", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", + "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", + "shasum": "" + }, + "require": { + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to normalize the trailing slash of the uri path", + "homepage": "https://github.com/middlewares/trailing-slash", + "keywords": [ + "http", + "middleware", + "normalize", + "path", + "psr-15", + "psr-7", + "slash" + ], + "support": { + "issues": "https://github.com/middlewares/trailing-slash/issues", + "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" + }, + "time": "2020-12-02T00:06:55+00:00" + }, + { + "name": "middlewares/utils", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/middlewares/utils.git", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v2.16", + "guzzlehttp/psr7": "^2.0", + "laminas/laminas-diactoros": "^2.4", + "nyholm/psr7": "^1.0", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "slim/psr7": "^1.4", + "squizlabs/php_codesniffer": "^3.5", + "sunrise/http-message": "^1.0", + "sunrise/http-server-request": "^1.0", + "sunrise/stream": "^1.0.15", + "sunrise/uri": "^1.0.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\Utils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Common utils for PSR-15 middleware packages", + "homepage": "https://github.com/middlewares/utils", + "keywords": [ + "PSR-11", + "http", + "middleware", + "psr-15", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/middlewares/utils/issues", + "source": "https://github.com/middlewares/utils/tree/v3.3.0" + }, + "time": "2021-07-04T17:56:23+00:00" + }, + { + "name": "middlewares/whoops", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/middlewares/whoops.git", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", + "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.5", + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^5.0 || ^7.0", + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to use Whoops as error handler", + "homepage": "https://github.com/middlewares/whoops", + "keywords": [ + "error", + "http", + "middleware", + "psr-15", + "psr-7", + "server", + "whoops" + ], + "support": { + "issues": "https://github.com/middlewares/whoops/issues", + "source": "https://github.com/middlewares/whoops/tree/v2.0.2" + }, + "time": "2022-01-27T20:31:30+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nette/schema", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", + "shasum": "" + }, + "require": { + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": ">=7.1 <8.2" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "phpstan/phpstan-nette": "^0.12", + "tracy/tracy": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.2.2" + }, + "time": "2021-10-15T11:40:02+00:00" + }, + { + "name": "nette/utils", + "version": "v3.2.7", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", + "shasum": "" + }, + "require": { + "php": ">=7.2 <8.2" + }, + "conflict": { + "nette/di": "<3.0.6" + }, + "require-dev": { + "nette/tester": "~2.0", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v3.2.7" + }, + "time": "2022-01-24T11:29:14+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", + "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-09-02T09:49:58+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/master" + }, + "time": "2018-10-30T17:12:04+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "shasum": "" + }, + "require": { + "php": ">=8.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:55:41+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-04T08:16:47+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "doctrine/coding-standard", + "version": "8.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/coding-standard.git", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Müller", + "email": "st.mueller@dzh-online.de" + } + ], + "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "doctrine", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/doctrine/coding-standard/issues", + "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" + }, + "time": "2021-04-03T10:54:55+00:00" + }, + { + "name": "mnapoli/hard-mode", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/hard-mode.git", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", + "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", + "shasum": "" + }, + "require": { + "doctrine/coding-standard": "^8.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Hard mode for PHP", + "support": { + "issues": "https://github.com/mnapoli/hard-mode/issues", + "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" + }, + "time": "2020-10-12T07:54:37+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", + "reference": "d0085a8083140e5203b1ce43add92f894b247e44", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" + }, + "time": "2022-03-18T17:23:40+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.5.4", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", + "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.5.4" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-04-03T12:39:00+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", + "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^1.2.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" + }, + "time": "2021-11-18T09:30:29+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", + "reference": "38358405ae948963c50a3aae3dfea598223ba15e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:58:14+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1", + "ext-apcu": "*", + "ext-zend-opcache": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/18-caching/config/dependencies.php b/implementation/18-caching/config/dependencies.php new file mode 100644 index 0000000..df815c6 --- /dev/null +++ b/implementation/18-caching/config/dependencies.php @@ -0,0 +1,58 @@ + fn (SystemClockNow $n) => $n, + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, + Emitter::class => fn (BasicEmitter $e) => $e, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, + MarkdownParser::class => fn (ParsedownParser $p) => $p, + MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, + EasyCache::class => fn (ApcuCache $c) => $c, + CachedMarkdownPageRepo::class => fn (EasyCache $c, FileSystemMarkdownPageRepo $r) => new CachedMarkdownPageRepo($c, $r), + + + // Factories + ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), + Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), +]; diff --git a/implementation/18-caching/config/middlewares.php b/implementation/18-caching/config/middlewares.php new file mode 100644 index 0000000..ab662be --- /dev/null +++ b/implementation/18-caching/config/middlewares.php @@ -0,0 +1,13 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/page', [Page::class, 'list']); + $r->addRoute('GET', '/page/{page}', [Page::class, 'show']); + $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); + $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/implementation/18-caching/config/settings.php b/implementation/18-caching/config/settings.php new file mode 100644 index 0000000..c654565 --- /dev/null +++ b/implementation/18-caching/config/settings.php @@ -0,0 +1,12 @@ +>](02-composer.md) + +### Front Controller + +A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. + +To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. + +A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. + +The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. + +But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. + +So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. + +Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: + +```php +>](02-composer.md) diff --git a/implementation/18-caching/data/pages/02-composer.md b/implementation/18-caching/data/pages/02-composer.md new file mode 100644 index 0000000..a25a4a8 --- /dev/null +++ b/implementation/18-caching/data/pages/02-composer.md @@ -0,0 +1,75 @@ +[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) + +### Composer + +[Composer](https://getcomposer.org/) is a dependency manager for PHP. + +Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do +something. With Composer, you can install third-party libraries for your application. + +If you don't have Composer installed already, head over to the website and install it. You can find Composer packages +for your project on [Packagist](https://packagist.org/). + +Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will +be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. + +Add the following content to the file: + +```json +{ + "name": "lubian/no-framework", + "require": { + "php": "^8.1" + }, + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "lubiana", + "email": "lubiana@hannover.ccc.de" + } + ] +} +``` + +In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use +whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. +Just replace it with your namespace in your own code. + +I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. + +As the Bootstrap.php file is placed in that directory we should +add the namespace to the File as well. Here is my current Bootstrap.php +as a reference: + +```php +>](03-error-handler.md) diff --git a/implementation/18-caching/data/pages/03-error-handler.md b/implementation/18-caching/data/pages/03-error-handler.md new file mode 100644 index 0000000..60465d0 --- /dev/null +++ b/implementation/18-caching/data/pages/03-error-handler.md @@ -0,0 +1,79 @@ +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) + +### Error Handler + +An error handler allows you to customize what happens if your code results in an error. + +A nice error page with a lot of information for debugging goes a long way during development. So the first package +for your application will take care of that. + +I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. +If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, +you have total control over your project. + +An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) + +To install a new package, open up your `composer.json` and add the package to the require part. It should now look +like this: + +```php +"require": { + "php": ">=8.1.0", + "filp/whoops": "^2.14" +}, +``` + +Now run `composer update` in your console and it will be installed. + +Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, +i that case composer automatically installs the package and updates your composer.json-file. + +But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, +ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you +only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. + +**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message +can help someone to gain access to your system. Always show a user friendly error page instead and send an email to +yourself, write to a log or something similar. So only you can see the errors in the production environment. + +For development that does not make sense though -- you want a nice error page. The solution is to have an environment +switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in +case no environment has been set. + +Then after the error handler registration, throw an `Exception` to test if everything is working correctly. +Your `Bootstrap.php` should now look similar to this: + +```php +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (\Throwable $e) { + error_log("Error: " . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +throw new \Exception("Ooooopsie"); + +``` + +You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until +you get it working. Now would also be a good time for another commit. + + +[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/implementation/18-caching/data/pages/04-development-helpers.md b/implementation/18-caching/data/pages/04-development-helpers.md new file mode 100644 index 0000000..74f913c --- /dev/null +++ b/implementation/18-caching/data/pages/04-development-helpers.md @@ -0,0 +1,260 @@ +[<< previous](03-error-handler.md) | [next >>](05-http.md) + +### Development Helpers + +I have added some more helpers to my composer.json that help me with development. As these are scripts and programms +used only for development they should not be used in a production environment. Composer has a specific sections in its +file called "dev-dependencies", everything that is required in this section does not get installen in production. + +Let's install our dev-helpers and i will explain them one by one: +`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` + +#### Static Code Analysis with phpstan + +Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or +create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements +that are not (or not always) available, if-statements that always are true and a lot of other stuff. + +A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. + +```php +/** + * @param \DateTime $date + * @return void + */ +function printDate($date) { + $date->format('Y-m-d H:i:s'); +} + +printDate('now'); +``` +if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` + +It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has +no use, and secondly, that we are calling the function with a string instead of a datetime object. + +```shell +1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + ------ --------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ --------------------------------------------------------------------------------------------- +30 Call to method DateTime::format() on a separate line has no effect. +33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. + ------ --------------------------------------------------------------------------------------------- +``` + +The second error is something that "declare strict-types" already catches for us, but the first error is something that +we usually would not discover easily without speccially looking for this errortype. + +We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and +path everytime we want to check our code for errors: + +```yaml +parameters: + level: max + paths: + - src +``` +now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project + +With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us +some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. + +```shell +composer require --dev phpstan/extension-installer +composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules +``` + +During the first install you need to allow the extension installer to actually install the extension. The second command +installs some more strict rulesets and activates them in phpstan. + +If we now rerun phpstan it already tells us about some errors we have made: + +``` + ------ ----------------------------------------------------------------------------------------------- +Line Bootstrap.php + ------ ----------------------------------------------------------------------------------------------- +10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider + using long ternary. +25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: + http://bit.ly/subtypeexception +26 Unreachable statement - code above always terminates. + ------ ----------------------------------------------------------------------------------------------- +``` + +The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove +that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific +problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working +on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages +everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we +are adding to our code. + +In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the +phpstan.neon file and run phpstan with the +'--generate-baseline' option: + +```yaml +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` +```shell +[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline +Note: Using configuration file /home/vagrant/app/phpstan.neon. + 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + + + [OK] Baseline generated with 1 error. + + +``` + +you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) + +#### PHP-CS-Fixer + +Another great tool is the php-cs-fixer, which just applies a specific style to your code. + +when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current +directory. + +You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) + +personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup +a configuration file that i use in all my projects .php-cs-fixer.php: + +```php +setRiskyAllowed(true) + ->setRules([ + '@PSR12:risky' => true, + '@PSR12' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP81Migration' => true, + 'array_indentation' => true, + 'include' => true, + 'blank_line_after_opening_tag' => false, + 'native_constant_invocation' => true, + 'new_with_braces' => false, + 'native_function_invocation' => [ + 'include' => ['@all'] + ], + 'no_unused_imports' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'ordered_interfaces' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([ + __DIR__ . '/src', + ]) + ); +``` + +#### PHP Codesniffer + +The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra +rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. + +it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. + +Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have +guessed we are adding some extra rules. + +Lets install the ruleset with composer +```shell +composer require --dev mnapoli/hard-mode +``` + +and add a configuration file to actually use it '.phpcs.xml.dist' +```xml + + + + + src + + + +``` + +running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. + +``` +[vagrant@archlinux app]$ ./vendor/bin/phpcs + +FILE: src/Bootstrap.php +---------------------------------------------------------------------------------------------------- +FOUND 4 ERRORS AFFECTING 4 LINES +---------------------------------------------------------------------------------------------------- + 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. + 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. + 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead +---------------------------------------------------------------------------------------------------- +PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY +---------------------------------------------------------------------------------------------------- + +Time: 639ms; Memory: 10MB +``` + +You can then use `./vendor/bin/phpcbf` to try to fix them + + +#### Symfony Var-Dumper + +another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small +functions. + +dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects +or arrays. + +dd() on the other hand is a function that dumps its parameters and then exits the php-script. + +you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. + +#### Composer scripts + +now we have a few commands that are available on the command line. i personally do not like to type complex commands +with lots of parameters by hand all the time, so i added a few lines to my composer.json: + +```json +"scripts": { + "serve": "php -S 0.0.0.0:1234 -t public", + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/phpcs", + "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" +}, +``` + +that way i can just type "composer" followed by the command name in the root of my project. if i want to start the +php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the +time. + +You could also configure PhpStorm to automatically run these commands in the background and highlight the violations +directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my +flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. + +My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before +commiting and pushing the code. + +[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/implementation/18-caching/data/pages/05-http.md b/implementation/18-caching/data/pages/05-http.md new file mode 100644 index 0000000..6166214 --- /dev/null +++ b/implementation/18-caching/data/pages/05-http.md @@ -0,0 +1,124 @@ +[<< previous](04-development-helpers.md) | [next >>](06-router.md) + +### HTTP + +PHP already has a few things built in to make working with HTTP easier. For example there are the +[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. + +These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, +if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, +then you will want a class with a nice object-oriented interface that you can use in your application instead. + +Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The +standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php +projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. + +As this is a widely adopted standard there are already several implementations available for us to use. I will choose +the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. + +Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a +[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. + +Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use +that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding +the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). + + +to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit +enter + +Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): + +```php +$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); +$response = new \Laminas\Diactoros\Response; +$response->getBody()->write('Hello World! '); +$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); +``` + +This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. + +In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the +write()-Method on that object. + + +To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: + +```php +echo $response->getBody(); +``` + +This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only +stores data. + +You can play around with the other methods of the Request object and take a look at its content with the dd() function. + +```php +dd($response) +``` + +Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot +be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which +creates a copy of the request object with the changed property and returns that clone: + +```php +$response = $response->withStatus(200); +$response = $response->withAddedHeader('Content-type', 'application/json'); +``` + +If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been +defined this way. + +But if you have been keeping attention you might argue that the following line should not work if the request object is +immutable. + +```php +$response->getBody()->write('Hello World!'); +``` + +The response-body implements a stream interface which is immutable for some reasons that are described in the +[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be +aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in +the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. +I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content +to a response object. + +```php +$body = $response->getBody(); +$body->write('Hello World!'); +$response = $response->withBody($body); +``` + +Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our +output-logic a little bit more. Replace the line that echos the response-body with the following: + +```php +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); +``` + +This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a +webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. + +Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. + +Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter + + +[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/implementation/18-caching/data/pages/06-router.md b/implementation/18-caching/data/pages/06-router.md new file mode 100644 index 0000000..6c39ae5 --- /dev/null +++ b/implementation/18-caching/data/pages/06-router.md @@ -0,0 +1,101 @@ +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) + +### Router + +A router dispatches to different handlers depending on rules that you have set up. + +With your current setup it does not matter what URL is used to access the application, it will always result in the same +response. So let's fix that now. + +I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own +favorite package. + +Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) + +By now you know how to install Composer packages, so I will leave that to you. + +Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. + +```php +$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}); + +$routeInfo = $dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), +); + +switch ($routeInfo[0]) { + case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: + $response = (new \Laminas\Diactoros\Response)->withStatus(405); + $response->getBody()->write('Method not allowed'); + $response = $response->withStatus(405); + break; + case \FastRoute\Dispatcher::FOUND: + $handler = $routeInfo[1]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + /** @var \Psr\Http\Message\ResponseInterface $response */ + $response = call_user_func($handler, $request); + break; + case \FastRoute\Dispatcher::NOT_FOUND: + default: + $response = (new \Laminas\Diactoros\Response)->withStatus(404); + $response->getBody()->write('Not Found!'); + break; +} +``` + +In the first part of the code, you are registering the available routes for your application. In the second part, the +dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, +we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. +If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. + +This setup might work for really small applications, but once you start adding a few routes your bootstrap file will +quickly get cluttered. So let's move them out into a separate file. + +Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; + +```php +addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { + $response = (new Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('This works too!'); + return $response; + }); +}; +``` + +Now let's rewrite the route dispatcher part to use the `Routes.php` file. + +```php +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); +``` + +This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. + +Of course we now need to add the 'config' folder to the configuration files of our +devhelpers so that they can scan that directory as well. + +[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/implementation/18-caching/data/pages/07-dispatching-to-a-class.md b/implementation/18-caching/data/pages/07-dispatching-to-a-class.md new file mode 100644 index 0000000..0c961a4 --- /dev/null +++ b/implementation/18-caching/data/pages/07-dispatching-to-a-class.md @@ -0,0 +1,137 @@ +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) + +### Dispatching to a Class + +In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). +MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to +learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) +and the followup posts. + +So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). + +We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other +common names are 'Controllers' or 'Actions'. + +Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. +In there, create a `Hello.php` file. + +```php +getAttribute('name', 'Stranger'); + $response = (new \Laminas\Diactoros\Response)->withStatus(200); + $response->getBody()->write('Hello ' . $name . '!'); + return $response; + } +} +``` + +You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) +that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is +fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement +it in some other parts of our application as well. In order to use that Interface we have to require it with composer: +'composer require psr/http-server-handler'. + +The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. +At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. + +Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: + +```php +return function(\FastRoute\RouteCollector $r) { + $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); + $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); +}; +``` + +Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared +the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing +the response to the one we defined for the second route. + +To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: + +```php +case \FastRoute\Dispatcher::FOUND: + $handler = new $routeInfo[1]; + if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { + throw new \Exception('Invalid Requesthandler'); + } + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + assert($response instanceof \Psr\Http\Message\ResponseInterface) + break; +``` + +So instead of just calling a method you are now instantiating an object and then calling the method on it. + +Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. + +And of course don't forget to commit your changes. + +Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still +generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for +example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still +have our whoopsie error-handler but i like to be more explicit in my control flow. + +In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new +Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and +InternalServerError. All three should extend phps Base Exception class. + +Here is my NotFound.php for example. + +```php + $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody()->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} +``` + +Check if our code still works, try to trigger some errors, run phpstan and the fix command +and don't forget to commit your changes. + +[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/implementation/18-caching/data/pages/08-inversion-of-control.md b/implementation/18-caching/data/pages/08-inversion-of-control.md new file mode 100644 index 0000000..21f4f23 --- /dev/null +++ b/implementation/18-caching/data/pages/08-inversion-of-control.md @@ -0,0 +1,54 @@ +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) + +### Inversion of Control + +In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we +want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then +we would need to edit every one of our request handlers to call a different constructor of the class. + +The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that +instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done +with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). + +If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is +implemented, it will make sense. + +Change your `Hello` action to the following: + +```php +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: + +```php +$handler = new $className($response); +``` + +Of course we need to also update all the other handlers. + +[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/implementation/18-caching/data/pages/09-dependency-injector.md b/implementation/18-caching/data/pages/09-dependency-injector.md new file mode 100644 index 0000000..7f7c6a2 --- /dev/null +++ b/implementation/18-caching/data/pages/09-dependency-injector.md @@ -0,0 +1,213 @@ +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) + +### Dependency Injector + +A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when +the class is instantiated. + +Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work +with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look +for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). + +I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) +out of the box. + +After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: + +```php +addDefinitions([ + \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), +]); + +return $builder->build(); +``` + +In this file we create a containerbuilder, add some definitions to it and return the container. +As the container supports autowiring we only need to define services where we want to use a specific implementation of +an interface. + +In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to +tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second +exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. + +Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), +as we will use that extensively. + +Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally +to create our Handler-Object + +```php +$container = require __DIR__ . '/../config/dependencies.php'; +assert($container instanceof \Psr\Container\ContainerInterface); + +$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); +assert($request instanceof \Psr\Http\Message\ServerRequestInterface); +``` + +The other part that has to be changed is the dispatching of the route. Before you had the following code: + +```php +$className = $routeInfo[1]; +$handler = new $className($response); +assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Change that to the following: + +```php +/** @var RequestHandlerInterface $handler */ +$className = $routeInfo[1]; +$handler = $container->get($className); +assert($handler instanceof RequestHandlerInterface); +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$response = $handler->handle($request); +``` + +Make sure to use the container fetch the response object in the catch blocks as well: + +```php +} catch (MethodNotAllowed) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(405); + $response->getBody()->write('Not Allowed'); +} catch (NotFound) { + $response = $container->get(ResponseInterface::class); + assert($response instanceof ResponseInterface); + $response = $response->withStatus(404); + $response->getBody()->write('Not Found'); +} +``` + +Now all your controller constructor dependencies will be automatically resolved with PHP-DI. + +We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons +in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call +`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we +want to work with a different date in a test. + +Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation +that creates us a DateTimeImmutable object with the current date and time. + +src/Service/Time/Now.php: +```php +namespace Lubian\NoFramework\Service\Time; + +interface Now +{ + public function __invoke(): \DateTimeImmutable; +} +``` +src/Service/Time/SystemClockNow.php: +```php +namespace Lubian\NoFramework\Service\Time; + +final class SystemClockNow implements Now +{ + + public function __invoke(): \DateTimeImmutable + { + return new \DateTimeImmutable; + } +} +``` +If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and +update the handle-method to use the new class property: + +```php +getAttribute('name', 'Stranger'); + $nowAsString = ($this->now)()->format('H:i:s'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowAsString); + + return $this->response + ->withBody($body) + ->withStatus(200); + } +} +``` + +If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI +automatically figures out what classes are requested in the constructor and tries to create the objects needed. + +But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred +S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: + +```php + public function __construct( + private ResponseInterface $response, + private Now $now, + ) +``` + +When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation +should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: + +```php +\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), +``` + +we could also use the PHP-DI create method to delegate the object creation to the container implementation: +```php +\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), +``` + +this way the container can try to resolve any dependencies that the class might have internally, but prefer the other +method because we are not depending on this specific dependency injection implementation. + +Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are +requesting the Hello action. + +If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As +we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way +we wont get any warnings for this particular issue, but for any other issues we add to our code. + +Update the phpstan.neon file to include a "baseline" file: + +``` +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src +``` + +if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and +ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just +'baseline' + +[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/implementation/18-caching/data/pages/10-invoker.md b/implementation/18-caching/data/pages/10-invoker.md new file mode 100644 index 0000000..3033fae --- /dev/null +++ b/implementation/18-caching/data/pages/10-invoker.md @@ -0,0 +1,102 @@ +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) + +### Invoker + +Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the +one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long +with some services and not the whole Requestobject with all its various properties. + +If we take our Hello action for example we only need a response object, the time service and the 'name' information from +the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named +the class hello and it would be redundant to also call the the method hello. So an updated version of that class could +look like this: + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + string $name = 'Stranger', + ): ResponseInterface + { + $body = $this->response->getBody(); + $nowString = $now->get()->format('H:i:s'); + + $body->write('Hello ' . $name . '!'); + $body->write(' The Time is ' . $nowString); + return $response + ->withBody($body) + ->withStatus(200); + } +} +``` + +It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short +closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the +rootpath of our application yet. + +```php +$r->addRoute('GET', '/hello[/{name}]', Hello::class); +$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); +$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +``` + +In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the +route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that +class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what +arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router +if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple +callable we would need to manually use reflection as well to resolve all the arguments. + +But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) +which handles all of that for us. + +After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo +switch section in the Bootstrap.php file to use the container->call() method: + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2]; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$response = $container->call($handler, $args); +``` + +Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. + +But by now you should know that I do not like to depend on specific implementations and the call method is not defined in +the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container +or any other implementation. + +Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific +container implementation so we could use it with any container that implements the ContainerInterface. And best of all +the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that +we could implement if we ever want to write our own implementation or we could write an adapter that uses a different +class that solves the same problem. + +But for now we are using the solution provided by PHP-DI. +So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block + +```php +$handler = $routeInfo[1]; +$args = $routeInfo[2] ?? []; +foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); +} +$args['request'] = $request; +$invoker = $container->get(InvokerInterface::class); +assert($invoker instanceof InvokerInterface); +$response = $invoker->call($handler, $args); +assert($response instanceof ResponseInterface); +``` + +Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) +by php, and even some more. + +But let us move on to something more fun and add some templating functionality to our application as we are trying to build +a website in the end. + +[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/implementation/18-caching/data/pages/11-templating.md b/implementation/18-caching/data/pages/11-templating.md new file mode 100644 index 0000000..7bfe1aa --- /dev/null +++ b/implementation/18-caching/data/pages/11-templating.md @@ -0,0 +1,236 @@ +[<< previous](10-invoker.md) | [next >>](12-configuration.md) + +### Templating + +A template engine is not necessary with PHP because the language itself can take care of that. But it can make things +like escaping values easier. They also make it easier to draw a clear line between your application logic and the +template files which should only put your variables into the HTML code. + +A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please +also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. +Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. + +For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install +that package before you continue (`composer require mustache/mustache`). + +Another well known alternative would be [Twig](http://twig.sensiolabs.org/). + +Now please go and have a look at the source code of the +[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class +does not implement an interface. + +You could just type hint against the concrete class. But the problem with this approach is that you create tight +coupling. + +In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the +implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want +to add functionality to the engine. You can't do that without going back and changing all your code that is tightly +coupled. + +What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need +another implementation, you just implement that interface in your new class and inject the new class instead. + +Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). +This sounds a lot more complicated than it is, so just follow along. + +First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). +This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. +A class can implement multiple interfaces if necessary. + +So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a +new folder in your `src/` folder with the name `Template` where you can put all the template related things. + +In there create a new interface `Renderer.php` that looks like this: + +```php + $data */ + public function render(string $template, array $data = []) : string; +} +``` + +Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file +`MustacheRenderer.php` with the following content: + +```php +engine->render($template, $data); + } +} +``` + +As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple +and only fulfills the interface. + +Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know +which implementation he has to inject when you hint for the interface. Add this line: + +```php +[ + ... + \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) + ->constructor(new Mustache_Engine), +] +``` + +Now update the Hello.php class to require an implementation of our renderer interface +and use that to render a string using mustache syntax. + + +```php +final class Hello +{ + public function __invoke( + ResponseInterface $response, + Now $now, + Renderer $renderer, + string $name = 'Stranger', + ): ResponseInterface { + $body = $response->getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render( + 'Hello {{name}}, the time is {{now}}!', + $data, + ); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} +``` + +Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. +But what we want is template files, so let's go back and change that. + +To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the +`dependencies.php` file and add the following code: + +```php +[ + ... + Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), + Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), +] +``` + +We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. +Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have +to rename all the template files. + +To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the +dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader +in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object +we can then use it in the factory. + +In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: + +``` +

Hello World

+Hello {{ name }} +``` + +Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` + +Navigate to the hello page in your browser to make sure everything works. + +One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies +file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration +values. + +Lets create a 'Settings' class in our './src' Folder: + +```php +addDefinitions([ + Settings::class => fn () => require __DIR__ '/settings.php', + ResponseInterface::class => create(Response::class), + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + Renderer::class => fn (ME $me) => new Mustache($me), + MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), +]); + +return $builder->build(); +``` + + + +And as always, don't forget to commit your changes. + + +[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/implementation/18-caching/data/pages/12-configuration.md b/implementation/18-caching/data/pages/12-configuration.md new file mode 100644 index 0000000..4b60c19 --- /dev/null +++ b/implementation/18-caching/data/pages/12-configuration.md @@ -0,0 +1,200 @@ +[<< previous](11-templating.md) | [next >>](13-refactoring.md) + +### Configuration + +In the last chapter we added some more definitions to our dependencies.php in that definitions +we needed to pass quite a few configuration settings and filesystem strings to the constructors +of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. + +As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. + +As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. + +Lets create a 'Settings' class in our './src' Folder: + +```php +filePath; + } +} +``` + +If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files +and craft a settings object from them. + +As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another +factory for our Container because everyone should have a Friend :) + +```php +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = fn () => $settings; + $builder->addDefinitions($dependencies); + return $builder->build(); + } +} +``` + +For this to work we need to change our dependencies.php file to just return the array of definitions: +And here we can instantly use the Settings object to create our template engine. + +```php + fn (ResponseFactory $rf) => $rf->createResponse(), + ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), + Now::class => fn (SystemClockNow $n) => $n, + Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), + MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), + ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), +]; +``` + +Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: + +```php +... +error_reporting(E_ALL); + +$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); +$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); +... +``` + +Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. + +[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/implementation/18-caching/data/pages/13-refactoring.md b/implementation/18-caching/data/pages/13-refactoring.md new file mode 100644 index 0000000..6dbbb8d --- /dev/null +++ b/implementation/18-caching/data/pages/13-refactoring.md @@ -0,0 +1,373 @@ +[<< previous](12-configuration.md) | [next >>](14-middleware.md) + +### Refactoring + +By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no +reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. +After all the bootstrap file should just set up the classes needed for the handling logic and execute them. + +At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. +As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the +method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non +static and extracted an interface. + +'./src/Http/Emitter.php' +```php +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + echo $response->getBody(); + } +} +``` +After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following +code in the Bootstrap.php to emit the response: + +```php +/** @var Emitter $emitter */ +$emitter = $container->get(Emitter::class); +$emitter->emit($response); +``` + +If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily +write an adapter that implements your emitter interface and wraps that more advanced emitter + +Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and +calling the routerhandler that in the passes the request to a function and gets the response. + +For this to steps to be seperated we are going to create two more classes: +1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object +2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the + requestobject, fetches the correct object from the container and calls it to create a response. + +Lets create the HandlerInterface first: + +```php +getAttribute($this->routeAttributeName, false); + assert($handler !== false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} + +``` + +We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. +The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler +as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in +the next chapter. + +```php +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} +``` + +Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. +```php +[ + '...', + Emitter::class => fn (BasicEmitter $e) => $e, + RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, + MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, + Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), + ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, +], +``` + +And then we can update our Bootstrap.php to fetch all the services and let them handle the request. + +```php +... +$routeMiddleWare = $container->get(MiddlewareInterface::class); +assert($routeMiddleWare instanceof MiddlewareInterface); +$handler = $container->get(RoutedRequestHandler::class); +assert($handler instanceof RequestHandlerInterface); +$emitter = $container->get(Emitter::class); +assert($emitter instanceof Emitter); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$response = $routeMiddleWare->process($request, $handler); +$emitter->emit($response); +``` +Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of +code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). + +So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. + +I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class +should require to function properly. + +* A RequestFactory + We want our Kernel to be able to build the request itself +* An Emitter + Without an Emitter we will not be able to send the response to the client +* RouteMiddleware + To decore the request with the correct handler for the requested route +* RequestHandler + To delegate the request to the correct funtion that creates the response + +As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface +to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our +interface: + +```php +factory::fromGlobals(); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} +``` + +For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: + +```php +routeMiddleware->process($request, $this->handler); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} + +``` + +We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines + +```php +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); +``` + +You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking +at the Whoops output and adding the needed definitions to the dependencies.php file. + +And as always, don't forget to commit your changes. + +[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/implementation/18-caching/data/pages/14-middleware.md b/implementation/18-caching/data/pages/14-middleware.md new file mode 100644 index 0000000..81f82a5 --- /dev/null +++ b/implementation/18-caching/data/pages/14-middleware.md @@ -0,0 +1,303 @@ +[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) + +### Middleware + +In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain +a bit more about what this interface does and why it is used in many applications. + +The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets +passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all +the middlewars to the client/emitter. + +So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the +response after it gets created by our handlers. + +So lets take a look at the middleware and the requesthandler interfaces + +```php +interface MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; +} + +interface RequestHandlerInterface +{ + public function handle(ServerRequestInterface $request): ResponseInterface; +} +``` + +The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a +requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the +response. + +But the middleware could just ignore the handler and produce a response on its own as the interface just requires us +to produce a response. + +A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users +that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the +database. + +In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates +the request with an 'isAuthenticated' attribute. + +If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that +response is not already cached, than we let the handler create the response and store that in the cache for a few +seconds + +```php +interface CacheInterface +{ + public function get(string $key, callable $resolver, int $ttl): mixed; +} +``` + +The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one +defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses +the callable to produce the value and stores it for the time specified in ttl. + +so lets write our caching middleware: + +```php +final class CachingMiddleware implements MiddlewareInterface +{ + public function __construct(private CacheInterface $cache){} + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { + $key = $request->getUri()->getPath(); + return $this->cache->get($key, fn() => $handler->handle($request), 10); + } + return $handler->handle($request); + } +} +``` + +we can also modify the response after it has been created by our application, for example we could implement a gzip +middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser +know that our application is used to serve dank memes: + +```php +final class DankMemeMiddleware implements MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $response = $handler->handle($request); + return $response->withAddedHeader('Meme', 'Dank'); + } +} +``` + +but for our application we are going to just add two external middlewares: + +* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. +* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware + +```bash +composer require middlewares/trailing-slash +composer require middlewares/whoops +``` + +The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the +application as well as the middleware stack. + +Our desired request -> response flow looks something like this: + + Client + | ^ + v | + Kernel + | ^ + v | + Whoops Middleware + | ^ + v | + TrailingSlash + | ^ + v | + Routing + | ^ + v | + ContainerResolver + | ^ + v | + Controller/Action + +As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every +middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. + +```php +interface Pipeline +{ + public function dispatch(ServerRequestInterface $request): ResponseInterface; +} +``` + +And our implementation looks something like this: + +```php + $middlewares + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} +``` + +Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code +that should produce our response. + +In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument +and store that itself as the current tip. + +There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) + +Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline +Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline + +```php +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} +``` + +And configure the container to use the Factory to create the Pipeline: + +```php + ..., + Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), + ... +``` +And of course a new file called middlewares.php in our config folder: +```php +pipeline->dispatch($request); +} +``` + +Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our +pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a +dependency container, if you want you can do that as well. + +**A quick note about docblocks:** You might have noticed, that I rarely add docblocks to my the code in the examples, and +when I do it seems kind of random. My philosophy is that I only add docblocks when there is no way to automatically get +the exact type from the code itself. For me docblocks only serve two purposes: help my IDE to understand what it choices +it has for code completion and to help the static analysis to better understand the code. There is a great blogpost +about the [cost and value of DocBlocks](https://localheinz.com/blog/2018/05/06/cost-and-value-of-docblocks/), although it +is written in 2018 at a time before PHP 7.4 was around everything written there is still valid today. + +[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) diff --git a/implementation/18-caching/data/pages/15-adding-content.md b/implementation/18-caching/data/pages/15-adding-content.md new file mode 100644 index 0000000..64562fa --- /dev/null +++ b/implementation/18-caching/data/pages/15-adding-content.md @@ -0,0 +1,253 @@ +[<< previous](14-middleware.md) | [next >>](16-data-repository.md) + +### Adding Content + +By now we did not really display anything but some examples to in our application and it is now time to make our app +display some content. For example we could our app be able to display the Markdown files used in this tutorial as +nicely rendered HTML Pages that can be viewed in the browser instead of the editor you are using. + +So lets start by copying the markdown files to our app directory. I have created a new folder 'data/pages' and placed all +the markdown files in there. + +Next we need a markdown parser, a pretty simple one is [Parsedown](https://parsedown.org/), if you want more features +you could also use the [Commonmark parser](https://commonmark.thephpleague.com/). I will choose Parsedown here, but you +can use whatever you like. + +After installing Parsedown lets write a Markdownparser interface and an implementation using parsedown. + +We only need one function that receives a string of Markdown and returns the HTML represantion (as a string as well). + + + +```php +parser->parse($markdown); + } +} +``` + +We could now use the ParsedownRender class directly in our actions by typehinting the classname as an argument to the +constructor or a method, but as we always want to rely on an interface instead of an implementation we need to define +the the ParsedownRenderer as the correct implementation for the MarkdownRenderer interface in the dependencies file: + +```php +... + \Lubian\NoFramework\Template\MarkdownParser::class => fn(\Lubian\NoFramework\Template\ParsedownParser $p) => $p, +... +``` + +You can test that in our "Other.php" action and try out if the Parser works and is able to render Markdown to HTML: + +```php +public function someFunctionName(ResponseInterface $response, MarkdownParser $parser): ResponseInterface +{ + $html = $parser->parse('This *works* **too!**'); + $response->getBody()->write($html); + return $response->withStatus(200); +} +``` + +But we want to display complete Pages written in Markdown, it would also be neat to be able to display a list of all +available pages. For that we need a few things: + +Firstly we need two new Templates, one for the list of the Pages, and the second one for displaying a single pages +content. Create a new folder in `templates/page` with to files: + +`templates/page/list.html` +```html + + + + + Pages + + + +
+ +
+ + +``` + +This template iterates over a provided array of pages, each element consists of the two properties: an id and a title, +those are simply displayed using an unordered list. + +`templates/page/show.html` +```html + + + + + {{title}} + + + + + + +
+ {{{content}}} +
+ + +``` + +The second templates displays a single rendered markdown page. As data it expects the title and the content as array. +I used an extra bracket for the content ```{{{content}}}``` so that the Mustache-Renderer does not escape the provided +html and thereby destroys the the parsed markdown. + +You might have spotted that I added [Pico.css](https://picocss.com/) which is just a very small css framework to make the +pages a little bit nicer to look at. It mostly provides some typography styles that work great with rendered Markdown, +but you can leave that out or use any other css framework you like. There is also some Javascript that adds syntax +highlighting to the code. + +After you have taken care of the templating side we can now create an new Action class with two methods to display use +our markdown files and the templates to create the pages. As we have two templates I propose to use Two methods in our +Action: +`src/Action/Page.php` +```php +function show(string $name): \Psr\Http\Message\ResponseInterface; +function list(): \Psr\Http\Message\ResponseInterface; +``` + +Lets define two routes. `/page` should display the overview of all pages, and if the add the name of chapter to the +route, `/page/adding-content` for example, the show action should be called with the name as a variable: + +`config/routes.php` +```php +$r->addRoute('GET', '/page', [Page::class, 'list']); +$r->addRoute('GET', '/page/{page}', [Page::class, 'show']); +``` + +Here is my Implementation. If have added a little regex replacement in the show method that replaces the links to the +next and previous chapter so that it works with our routing configuration. + +`src/Action/Page.php` +```php +getPages(), + fn (string $filename) => str_contains($filename, $page) + ) + )[0]; + $markdown = file_get_contents($page); + + // fix the next and previous buttons to work with our routing + $markdown = preg_replace('/\(\d\d-/m', '(', $markdown); + $markdown = str_replace('.md)', ')', $markdown); + + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + $data = [ + 'title' => substr($page, 3), + 'content' => $this->parser->parse($markdown), + ]; + $html = $this->renderer->render('page/show', $data); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map(function (string $page) { + $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); + return [ + 'id' => substr($page, 0, 2), + 'title' => substr($page, 3), + ]; + }, $this->getPages()); + $html = $this->renderer->render('page/list', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } + + /** + * @return string[] + */ + private function getPages(): array + { + $files = glob($this->pagesPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return $files; + } +} +``` + +You can now navigate your Browser to [localhost:1234/page][http://localhost:1234/page] and try out if everything works. + +Of course this code is far from looking good. We heavily rely on the pages being files in the filesystem, and the action +should never be aware of the filesystem in the first place, also we have a lot of string replacements and other repetetive +code in the file. And phpstan is gonna scream at us a lot, but if we rewrite the code to satisfy all the checks we would +add even more lines to that simple class, so lets move on to the next chapter where we move all the logic to seperate +classes following our holy SOLID principles :) + + +[<< previous](14-middleware.md) | [next >>](16-data-repository.md) diff --git a/implementation/18-caching/data/pages/16-data-repository.md b/implementation/18-caching/data/pages/16-data-repository.md new file mode 100644 index 0000000..d9a3218 --- /dev/null +++ b/implementation/18-caching/data/pages/16-data-repository.md @@ -0,0 +1,265 @@ +[<< previous](15-adding-content.md) | [next >>](17-performance.md) + +## Data Repository + +At the end of the last chapter I mentioned being unhappy with our Pages action, because there is to much stuff happening +there. We are firstly receiving some Arguments, then we are using those to query the filesytem for the given page, +loading the specific file from the filesystem, rendering the markdown, passing the markdown to the template renderer, +adding the resulting html to the response and then returning the response. + +In order to make our pageaction independent from the filesystem and move the code that is responsible for reading the +files +to a better place I want to introduce +the [Repository Pattern](https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html). + +I want to start by creating a class that represents the Data that is included in a page so that. For now I can spot +three +distrinct attributes. + +* the ID (or chapternumber) +* the title (or name) +* the content + +Currently all those properties are always available, but we might later be able to create new pages and store them, but +at that point in time we are not yet aware of the new available ID, so we should leave that property nullable. This +allows +us to create an object without an id and let the code that actually saves the object to a persistant store define a +valid +id on saving. + +Lets create an new Namespace called `Model` and put a `MarkdownPage.php` class in there: + +```php +dataPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return array_map(function (string $filename) { + $content = file_get_contents($filename); + if ($content === false) { + throw new InternalServerError('cannot read pages'); + } + $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); + return new MarkdownPage( + (int) substr($idAndTitle, 0, 2), + substr($idAndTitle, 3), + $content + ); + }, $files); + } + + public function byName(string $name): MarkdownPage + { + $pages = array_values( + array_filter( + $this->all(), + fn (MarkdownPage $p) => $p->title === $name, + ) + ); + + if (count($pages) !== 1) { + throw new NotFound; + } + + return $pages[0]; + } +} +``` + +With that in place we need to add the required `$pagesPath` to our settings class and add specify that in our +configuration. + +`src/Settings.php` + +```php +final class Settings +{ + public function __construct( + public readonly string $environment, + public readonly string $dependenciesFile, + public readonly string $middlewaresFile, + public readonly string $templateDir, + public readonly string $templateExtension, + public readonly string $pagesPath, + ) { + } +} +``` + +`config/settings.php` + +```php +return new Settings( + environment: 'prod', + dependenciesFile: __DIR__ . '/dependencies.php', + middlewaresFile: __DIR__ . '/middlewares.php', + templateDir: __DIR__ . '/../templates', + templateExtension: '.html', + pagesPath: __DIR__ . '/../data/pages/', +); +``` + +Of course we need to define the correct implementation for the container to choose when we are requesting the Repository +interface: +`conf/dependencies.php` + +```php +MarkdownPageRepo::class => fn (FileSystemMarkdownPageRepo $r) => $r, +FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), +``` + +Now you can request the MarkdownPageRepo Interface in your page action and use the defined functions to get the +MarkdownPage +Objects. My `src/Action/Page.php` looks like this now: + +```php +repo->byName($page); + + // fix the next and previous buttons to work with our routing + $content = preg_replace('/\(\d\d-/m', '(', $page->content); + assert(is_string($content)); + $content = str_replace('.md)', ')', $content); + + $data = [ + 'title' => $page->title, + 'content' => $this->parser->parse($content), + ]; + + $html = $this->renderer->render('page/show', $data); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map(function (MarkdownPage $page) { + return [ + 'id' => $page->id, + 'title' => $page->content, + ]; + }, $this->repo->all()); + + $html = $this->renderer->render('page/list', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } +} +``` + +Check the page in your browser if everything still works, don't forget to run phpstan and the others fixers before +committing your changes and moving on to the next chapter. + +[<< previous](15-adding-content.md) | [next >>](17-performance.md) diff --git a/implementation/18-caching/data/pages/17-performance.md b/implementation/18-caching/data/pages/17-performance.md new file mode 100644 index 0000000..c83c7d5 --- /dev/null +++ b/implementation/18-caching/data/pages/17-performance.md @@ -0,0 +1,43 @@ +[<< previous](16-data-repository.md) | [next >>](18-caching.md) + +## Autoloading performance + +Although our application is still very small and you should not really experience any performance issues right now, +there are still some things we can already consider and take a look at. If I check the network tab in my browser it takes +about 90-400ms to show a simple rendered markdownpage, with is sort of ok but in my opinion way to long as we are not +really doing anything and do not connect to any external services. Mostly we are just reading around 16 markdown files, +a template, some config files here and there and parse some markdown. So that should not really take that long. + +The problem is, that we heavily rely on autoloading for all our class files, in the `src` folder. And there are also +quite a lot of other files in composers `vendor` directory. To understand while this is becomming we should make +ourselves familiar with how [autoloading in php](https://www.php.net/manual/en/language.oop5.autoload.php) works. + +The basic idea is, that every class that php encounters has to be loaded from somewhere in the filesystem, we could +just require the files manually but that is tedious, unflexible and can often cause errors. + +The problem we are now facing is that the composer autoloader has some rules to determine from where in the filesystem +a class definition might be placed, then the autoloader tries to locate a file by the namespace and classname and if it +exists includes that file. + +If we only have a handfull of classes that does not take a lot of time, but as we are growing with our application this +easily takes longer than necesery, but fortunately composer has some options to speed up the class loading. + +Take a few minutes to read the documentation about [composer autoloader optimization](https://getcomposer.org/doc/articles/autoloader-optimization.md) + +You can try all 3 levels of optimizations, but we are going to stick with the first one for now, so lets create an +optimized classmap. + +`composer dump-autoload -o` + +After composer has finished you can start the devserver again with `composer serve` and take a look at the network tab +in your browsers devtools. + +In my case the response time falls down to under an average of 30ms with some spikes in between, but all in all it looks really good. +You can also try out the different optimization levels and see if you can spot any differences. + +Although the composer manual states not to use the optimtization in a dev environment I personally have not encountered +any errors with the first level of optimizations, so we can use that level here. If you add the line from the documentation +to your `composer.json` so that the autoloader gets optimized everytime we install new packages. + + +[<< previous](16-data-repository.md) | [next >>](18-caching.md) diff --git a/implementation/18-caching/data/pages/18-caching.md b/implementation/18-caching/data/pages/18-caching.md new file mode 100644 index 0000000..42e9cb1 --- /dev/null +++ b/implementation/18-caching/data/pages/18-caching.md @@ -0,0 +1,252 @@ +[<< previous](17-performance.md) | [next >>](19-database.md) + +**DISClAIMER** I do not really have a lot of experience when it comes to caching, so this chapter is mostly some random +thoughts and ideas I wanted to explore when writing this tutorial, you should definitely take everything that is being +said here with caution and try to read up on some other sources. But that holds true for the whole tutorial anyway :) + +## Caching + +In the last chapter we greatly improved the perfomance for the lookup of all our classfiles, but currently we do not +have any real bottlenecks in our application like complex queries. + +But in a real application we are going to execute some really heavy and time intensive database queries that can take +quite a while to be completed. + +We can simulate that by adding a simple delay in our `FileSystemMarkdownPageRepo`. + +```php + return array_map(function (string $filename) { + usleep(rand(100, 400) * 1000); + $content = file_get_contents($filename); + if ($content === false) { + throw new InternalServerError('cannot read pages'); + } + $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); + return new MarkdownPage( + (int) substr($idAndTitle, 0, 2), + substr($idAndTitle, 3), + $content + ); +}); +``` + +Here I added a function that pauses the scripts execution for a random time between 100 and 400ms for every markdownpage +in every call of the `all()` method. + +If you open any page or even the listAction in you browser you will see, that it takes quite a time to render that page. +Although this is a silly example we do not really need to query the database on every request, so lets add a way to cache +the database results between requests. + +The PHP-Community has already adressed the issue of having easy to use access to cache libraries, there is the +[PSR-6 Caching Interface](https://www.php-fig.org/psr/psr-6) which gives us easy access to many different implementations, +then there is also a much simpler [PSR-16 Simple Cache](https://www.php-fig.org/psr/psr-16) which makes the use even more +easy, and most Caching Libraries implement Both interfaces anyway. You would think that this is more than enough solutions +to satisfy all the Caching needs around, but the Symfony People decided that Caching should be even simpler and easier +to use and defined their own [Interface](https://symfony.com/doc/current/components/cache.html#cache-component-contracts) +which only needs two methods. You should definitely take a look at the linked documentation as it really blew my mind +when I first encountered it. + +The basic idea is that you provide a callback that computes the requested value. The Cache implementation then checks +if it already has the value stored somewhere and if it doesnt it just executes the callback and stores the value for +future calls. + +It is really simple and great to use. In a real world application you should definitely use that or a PSR-16 implementation +but for this tutorial I wanted to roll out my own solution, so here we go. + +As always we are going to define an interface first, I am going to call it EasyCache and place it in the `Service/Cache` +namespace. I will require only one method which is base on the Symfony Cache Contract, and hast a key, a callback, and +the duration that the item should be cached as arguments. + +```php +cache->get( + $key, + fn () => $this->repo->all(), + 300 + ); + } + + public function byName(string $name): MarkdownPage + { + $key = base64_encode(self::class . 'byName' . $name); + return $this->cache->get( + $key, + fn () => $this->repo->byName($name), + 300 + ); + } +} +``` + +This simple wrapper just requires an EasyCache implementation and a MarkdownPageRepo in the constructor and uses them +to cache all queries for 5 minutes. The beauty is that we are not dependent on any implementation here, so we can switch +out the Repository or the Cache at any point down the road if we want to. + +In order to use that we need to update our `config/dependencies.php` to add an alias for the EasyCache interface as well +as defining our CachedMarkdownPageRepo as implementation for the MarkdownPageRepo interface: + +```php +MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, +EasyCache::class => fn (ApcuCache $c) => $c, +``` + +If we try to access our webpage now, we are getting an error, as PHP-DI has detected a circular dependency that cannot +be autowired. + +The Problem is that our CachedMarkdownPageRepo ist defined as the implementation for the MarkdownPageRepo, but it also +requires that exact interface as a dependency. To resolve this issue we need to manually tell the container how to build +the CachedMarkdownPageRepo by adding another line to the `config/dependencies.php` file: + +```php +CachedMarkdownPageRepo::class => fn (EasyCache $c, FileSystemMarkdownPageRepo $r) => new CachedMarkdownPageRepo($c, $r), +``` + +Here we explicitly require the FileSystemMarkdownPageRepo and us that to create the CachedMarkdownPageRepo object. + +When you now navigate to the pages list or to a specific page the first load should take a while (because of our added delay) +but the following request should be answered blazingly fast. + +Before moving on to the next chapter we can take the caching approach even further, in the middleware chapter I talked +about a simple CachingMiddleware that caches all the GET-Request for some seconds, as they should not change that often, +and we can bypass most of our application logic if we just complelety cache away the responses our application generates, +and return them quite early in our Middleware-Pipeline befor the router gets called, or the invoker calls the action, +which itself uses some other services to fetch all the needed data. + +We will introduce a new `Middleware` namespace to place our `Cache.php` middleware: +```php +getMethod() !== 'GET') { + return $handler->handle($request); + } + $keyHash = base64_encode($request->getUri()->getPath()); + $result = $this->cache->get( + $keyHash, + fn () => Serializer::toString($handler->handle($request)), + 300 + ); + return Serializer::fromString($result); + } +} +``` + +The code is quite straight forward, but you might be confused by the Responseserializer I have added here, we need this +because the response body is a stream object, which doesnt always gets serialized correctly, therefore I use a class from +the laminas project to to all the heavy lifting for us. + +We need to add the now middleware to the `config/middlewares.php` file. + +```php +>](19-database.md) diff --git a/implementation/18-caching/phpstan-baseline.neon b/implementation/18-caching/phpstan-baseline.neon new file mode 100644 index 0000000..61697a1 --- /dev/null +++ b/implementation/18-caching/phpstan-baseline.neon @@ -0,0 +1,7 @@ +parameters: + ignoreErrors: + - + message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" + count: 1 + path: src/Http/InvokerRoutedHandler.php + diff --git a/implementation/18-caching/phpstan.neon b/implementation/18-caching/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/18-caching/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/18-caching/public/css/spectre-exp.min.css b/implementation/18-caching/public/css/spectre-exp.min.css new file mode 100644 index 0000000..d313774 --- /dev/null +++ b/implementation/18-caching/public/css/spectre-exp.min.css @@ -0,0 +1 @@ +/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/implementation/18-caching/public/css/spectre-icons.min.css b/implementation/18-caching/public/css/spectre-icons.min.css new file mode 100644 index 0000000..0276f7b --- /dev/null +++ b/implementation/18-caching/public/css/spectre-icons.min.css @@ -0,0 +1 @@ +/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/implementation/18-caching/public/css/spectre.min.css b/implementation/18-caching/public/css/spectre.min.css new file mode 100644 index 0000000..0fe23d9 --- /dev/null +++ b/implementation/18-caching/public/css/spectre.min.css @@ -0,0 +1 @@ +/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/implementation/18-caching/public/favicon.ico b/implementation/18-caching/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/18-caching/public/index.php b/implementation/18-caching/public/index.php new file mode 100644 index 0000000..32f5eb3 --- /dev/null +++ b/implementation/18-caching/public/index.php @@ -0,0 +1,5 @@ +getBody(); + $data = [ + 'now' => $now()->format('H:i:s'), + 'name' => $name, + ]; + + $content = $renderer->render('hello', $data); + + $body->write($content); + + return $response + ->withStatus(200) + ->withBody($body); + } +} diff --git a/implementation/18-caching/src/Action/Other.php b/implementation/18-caching/src/Action/Other.php new file mode 100644 index 0000000..da9ceaf --- /dev/null +++ b/implementation/18-caching/src/Action/Other.php @@ -0,0 +1,16 @@ +parse('This *works* **too!**'); + $response->getBody()->write($html); + return $response->withStatus(200); + } +} diff --git a/implementation/18-caching/src/Action/Page.php b/implementation/18-caching/src/Action/Page.php new file mode 100644 index 0000000..96696e4 --- /dev/null +++ b/implementation/18-caching/src/Action/Page.php @@ -0,0 +1,60 @@ +repo->byName($page); + + // fix the next and previous buttons to work with our routing + $content = preg_replace('/\(\d\d-/m', '(', $page->content); + assert(is_string($content)); + $content = str_replace('.md)', ')', $content); + + $data = [ + 'title' => $page->title, + 'content' => $this->parser->parse($content), + ]; + + $html = $this->renderer->render('page/show', $data); + $this->response->getBody()->write($html); + return $this->response; + } + + public function list(): ResponseInterface + { + $pages = array_map(function (MarkdownPage $page) { + return [ + 'id' => $page->id, + 'title' => $page->title, + ]; + }, $this->repo->all()); + + $html = $this->renderer->render('page/list', ['pages' => $pages]); + $this->response->getBody()->write($html); + return $this->response; + } +} diff --git a/implementation/18-caching/src/Bootstrap.php b/implementation/18-caching/src/Bootstrap.php new file mode 100644 index 0000000..3abc2e5 --- /dev/null +++ b/implementation/18-caching/src/Bootstrap.php @@ -0,0 +1,40 @@ +getContainer(); + +$settings = $settingsProvider->getSettings(); + +$whoops = new Run; +if ($settings->environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $e): void { + error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); + echo 'An Error happened'; + }); +} +$whoops->register(); + +$app = $container->get(Kernel::class); +assert($app instanceof Kernel); + +$app->run(); diff --git a/implementation/18-caching/src/Exception/InternalServerError.php b/implementation/18-caching/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/18-caching/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +factory::fromGlobals(); + } + + /** + * @param UriInterface|string $uri + * @param array $serverParams + */ + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return $this->factory->createServerRequest($method, $uri, $serverParams); + } +} diff --git a/implementation/18-caching/src/Factory/FileSystemSettingsProvider.php b/implementation/18-caching/src/Factory/FileSystemSettingsProvider.php new file mode 100644 index 0000000..f071078 --- /dev/null +++ b/implementation/18-caching/src/Factory/FileSystemSettingsProvider.php @@ -0,0 +1,22 @@ +filePath; + assert($settings instanceof Settings); + return $settings; + } +} diff --git a/implementation/18-caching/src/Factory/PipelineProvider.php b/implementation/18-caching/src/Factory/PipelineProvider.php new file mode 100644 index 0000000..77738f8 --- /dev/null +++ b/implementation/18-caching/src/Factory/PipelineProvider.php @@ -0,0 +1,25 @@ +settings->middlewaresFile; + return new ContainerPipeline($middlewares, $this->tip, $this->container); + } +} diff --git a/implementation/18-caching/src/Factory/RequestFactory.php b/implementation/18-caching/src/Factory/RequestFactory.php new file mode 100644 index 0000000..2b17abc --- /dev/null +++ b/implementation/18-caching/src/Factory/RequestFactory.php @@ -0,0 +1,11 @@ +settingsProvider->getSettings(); + $dependencies = require $settings->dependenciesFile; + $dependencies[Settings::class] = $settings; + $builder->addDefinitions($dependencies); + // $builder->enableCompilation('/tmp'); + return $builder->build(); + } +} diff --git a/implementation/18-caching/src/Factory/SettingsProvider.php b/implementation/18-caching/src/Factory/SettingsProvider.php new file mode 100644 index 0000000..ce1c5f0 --- /dev/null +++ b/implementation/18-caching/src/Factory/SettingsProvider.php @@ -0,0 +1,10 @@ +getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + + if ($withoutBody) { + return; + } + + echo $response->getBody(); + } +} diff --git a/implementation/18-caching/src/Http/ContainerPipeline.php b/implementation/18-caching/src/Http/ContainerPipeline.php new file mode 100644 index 0000000..816cedd --- /dev/null +++ b/implementation/18-caching/src/Http/ContainerPipeline.php @@ -0,0 +1,82 @@ + $middlewares + * @param RequestHandlerInterface $tip + * @param ContainerInterface $container + */ + public function __construct( + private array $middlewares, + private RequestHandlerInterface $tip, + private ContainerInterface $container, + ) { + } + + public function dispatch(ServerRequestInterface $request): ResponseInterface + { + $this->buildStack(); + return $this->tip->handle($request); + } + + private function buildStack(): void + { + foreach (array_reverse($this->middlewares) as $middleware) { + $next = $this->tip; + if ($middleware instanceof MiddlewareInterface) { + $this->tip = $this->wrapMiddleware($middleware, $next); + } + if (is_string($middleware)) { + $this->tip = $this->wrapResolvedMiddleware($middleware, $next); + } + } + } + + private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { + public function __construct( + private readonly string $middleware, + private readonly RequestHandlerInterface $handler, + private readonly ContainerInterface $container, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = $this->container->get($this->middleware); + assert($middleware instanceof MiddlewareInterface); + return $middleware->process($request, $this->handler); + } + }; + } + + private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface + { + return new class ($middleware, $next) implements RequestHandlerInterface { + public function __construct( + private readonly MiddlewareInterface $middleware, + private readonly RequestHandlerInterface $handler, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->handler); + } + }; + } +} diff --git a/implementation/18-caching/src/Http/Emitter.php b/implementation/18-caching/src/Http/Emitter.php new file mode 100644 index 0000000..ce4c035 --- /dev/null +++ b/implementation/18-caching/src/Http/Emitter.php @@ -0,0 +1,10 @@ +getAttribute($this->routeAttributeName, false); + $vars = $request->getAttributes(); + $vars['request'] = $request; + $response = $this->invoker->call($handler, $vars); + if (! $response instanceof ResponseInterface) { + throw new InternalServerError('Handler returned invalid response'); + } + return $response; + } + + public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void + { + $this->routeAttributeName = $routeAttributeName; + } +} diff --git a/implementation/18-caching/src/Http/Pipeline.php b/implementation/18-caching/src/Http/Pipeline.php new file mode 100644 index 0000000..1a9dcda --- /dev/null +++ b/implementation/18-caching/src/Http/Pipeline.php @@ -0,0 +1,11 @@ +dispatcher->dispatch( + $request->getMethod(), + $request->getUri()->getPath(), + ); + + if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new MethodNotAllowed; + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + return $request->withAttribute( + $this->routeAttributeName, + $routeInfo[1] + ); + } + + throw new NotFound; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + $request = $this->decorateRequest($request); + } catch (NotFound) { + $response = $this->responseFactory->createResponse(404); + $response->getBody()->write('Not Found'); + return $response; + } catch (MethodNotAllowed) { + return $this->responseFactory->createResponse(405); + } catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); + } + + if ($handler instanceof RoutedRequestHandler) { + $handler->setRouteAttributeName($this->routeAttributeName); + } + return $handler->handle($request); + } +} diff --git a/implementation/18-caching/src/Http/RoutedRequestHandler.php b/implementation/18-caching/src/Http/RoutedRequestHandler.php new file mode 100644 index 0000000..a7407c9 --- /dev/null +++ b/implementation/18-caching/src/Http/RoutedRequestHandler.php @@ -0,0 +1,10 @@ +pipeline->dispatch($request); + } + + public function run(): void + { + $request = $this->requestFactory->fromGlobals(); + $response = $this->handle($request); + $this->emitter->emit($response); + } +} diff --git a/implementation/18-caching/src/Middleware/Cache.php b/implementation/18-caching/src/Middleware/Cache.php new file mode 100644 index 0000000..8460761 --- /dev/null +++ b/implementation/18-caching/src/Middleware/Cache.php @@ -0,0 +1,38 @@ +getMethod() !== 'GET') { + return $handler->handle($request); + } + $keyHash = base64_encode($request->getUri()->getPath()); + $result = $this->cache->get( + $keyHash, + fn () => $this->serializer::toString($handler->handle($request)), + 300 + ); + assert(is_string($result)); + return $this->serializer::fromString($result); + } +} diff --git a/implementation/18-caching/src/Model/MarkdownPage.php b/implementation/18-caching/src/Model/MarkdownPage.php new file mode 100644 index 0000000..df244fd --- /dev/null +++ b/implementation/18-caching/src/Model/MarkdownPage.php @@ -0,0 +1,13 @@ +cache->get( + $key, + fn () => $this->repo->all(), + 300 + ); + assert(is_array($result)); + foreach ($result as $page) { + assert($page instanceof MarkdownPage); + } + return $result; + } + + public function byName(string $name): MarkdownPage + { + $key = base64_encode(self::class . 'byName' . $name); + $result = $this->cache->get( + $key, + fn () => $this->repo->byName($name), + 300 + ); + assert($result instanceof MarkdownPage); + return $result; + } +} diff --git a/implementation/18-caching/src/Repository/FileSystemMarkdownPageRepo.php b/implementation/18-caching/src/Repository/FileSystemMarkdownPageRepo.php new file mode 100644 index 0000000..cca350e --- /dev/null +++ b/implementation/18-caching/src/Repository/FileSystemMarkdownPageRepo.php @@ -0,0 +1,61 @@ +dataPath . '*.md'); + if ($files === false) { + throw new InternalServerError('cannot read pages'); + } + return array_map(function (string $filename) { + $content = file_get_contents($filename); + if ($content === false) { + throw new InternalServerError('cannot read pages'); + } + $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); + return new MarkdownPage( + (int) substr($idAndTitle, 0, 2), + substr($idAndTitle, 3), + $content + ); + }, $files); + } + + public function byName(string $name): MarkdownPage + { + $pages = array_values( + array_filter( + $this->all(), + fn (MarkdownPage $p) => $p->title === $name, + ) + ); + + if (count($pages) !== 1) { + throw new NotFound; + } + + return $pages[0]; + } +} diff --git a/implementation/18-caching/src/Repository/MarkdownPageRepo.php b/implementation/18-caching/src/Repository/MarkdownPageRepo.php new file mode 100644 index 0000000..0792d32 --- /dev/null +++ b/implementation/18-caching/src/Repository/MarkdownPageRepo.php @@ -0,0 +1,15 @@ +engine->render($template, $data); + } +} diff --git a/implementation/18-caching/src/Template/ParsedownParser.php b/implementation/18-caching/src/Template/ParsedownParser.php new file mode 100644 index 0000000..2ffd287 --- /dev/null +++ b/implementation/18-caching/src/Template/ParsedownParser.php @@ -0,0 +1,17 @@ +parser->parse($markdown); + } +} diff --git a/implementation/18-caching/src/Template/Renderer.php b/implementation/18-caching/src/Template/Renderer.php new file mode 100644 index 0000000..ff916ed --- /dev/null +++ b/implementation/18-caching/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data = []): string; +} diff --git a/implementation/18-caching/templates/hello.html b/implementation/18-caching/templates/hello.html new file mode 100644 index 0000000..15a4cd2 --- /dev/null +++ b/implementation/18-caching/templates/hello.html @@ -0,0 +1,6 @@ +{{> partials/head }} +
+

Hello {{name}}

+

The time is {{now}}

+
+{{> partials/foot }} diff --git a/implementation/18-caching/templates/page.html b/implementation/18-caching/templates/page.html new file mode 100644 index 0000000..c3c5284 --- /dev/null +++ b/implementation/18-caching/templates/page.html @@ -0,0 +1,5 @@ +{{> partials/head }} +
+ {{{content}}} +
+{{> partials/foot }} diff --git a/implementation/18-caching/templates/page/list.html b/implementation/18-caching/templates/page/list.html new file mode 100644 index 0000000..bf42348 --- /dev/null +++ b/implementation/18-caching/templates/page/list.html @@ -0,0 +1,19 @@ + + + + + Pages + + + +
+ +
+ + \ No newline at end of file diff --git a/implementation/18-caching/templates/page/show.html b/implementation/18-caching/templates/page/show.html new file mode 100644 index 0000000..abe295e --- /dev/null +++ b/implementation/18-caching/templates/page/show.html @@ -0,0 +1,17 @@ + + + + + {{title}} + + + + + + +
+ {{{content}}} +
+ + \ No newline at end of file diff --git a/implementation/18-caching/templates/pagelist.html b/implementation/18-caching/templates/pagelist.html new file mode 100644 index 0000000..538e2c4 --- /dev/null +++ b/implementation/18-caching/templates/pagelist.html @@ -0,0 +1,11 @@ +{{> partials/head }} +
+ +
+{{> partials/foot }} diff --git a/implementation/18-caching/templates/partials/foot.html b/implementation/18-caching/templates/partials/foot.html new file mode 100644 index 0000000..17c7245 --- /dev/null +++ b/implementation/18-caching/templates/partials/foot.html @@ -0,0 +1,3 @@ +
+ + \ No newline at end of file diff --git a/implementation/18-caching/templates/partials/head.html b/implementation/18-caching/templates/partials/head.html new file mode 100644 index 0000000..421d387 --- /dev/null +++ b/implementation/18-caching/templates/partials/head.html @@ -0,0 +1,11 @@ + + + + + No Framework: {{title}} + + + + + +
From 2a4db316b97b99fde2358ed4337c6a292d285f77 Mon Sep 17 00:00:00 2001 From: lubiana Date: Thu, 14 Apr 2022 07:48:16 +0200 Subject: [PATCH 281/314] prepare --- app/.php-cs-fixer.php | 38 - app/.phpcs.xml.dist | 9 - app/cli-config.php | 13 - app/composer.json | 57 - app/composer.lock | 2440 ---------- app/config/dependencies.php | 58 - app/config/middlewares.php | 13 - app/config/routes.php | 15 - app/config/settings.php | 12 - app/data/pages/01-front-controller.md | 53 - app/data/pages/02-composer.md | 75 - app/data/pages/03-error-handler.md | 79 - app/data/pages/04-development-helpers.md | 260 - app/data/pages/05-http.md | 124 - app/data/pages/06-router.md | 101 - app/data/pages/07-dispatching-to-a-class.md | 137 - app/data/pages/08-inversion-of-control.md | 54 - app/data/pages/09-dependency-injector.md | 213 - app/data/pages/10-invoker.md | 102 - app/data/pages/11-templating.md | 236 - app/data/pages/12-configuration.md | 200 - app/data/pages/13-refactoring.md | 373 -- app/data/pages/14-middleware.md | 303 -- app/data/pages/15-adding-content.md | 253 - app/data/pages/16-data-repository.md | 265 - app/data/pages/17-performance.md | 43 - app/data/pages/18-caching.md | 252 - app/phpstan-baseline.neon | 7 - app/phpstan.neon | 8 - app/public/css/spectre-exp.min.css | 1 - app/public/css/spectre-icons.min.css | 1 - app/public/css/spectre.min.css | 1 - app/public/index.php | 5 - app/src/.gitkeep | 0 app/src/Action/Hello.php | 31 - app/src/Action/Other.php | 16 - app/src/Action/Page.php | 60 - app/src/Bootstrap.php | 40 - app/src/Exception/InternalServerError.php | 9 - app/src/Exception/MethodNotAllowed.php | 9 - app/src/Exception/NotFound.php | 9 - app/src/Factory/ContainerProvider.php | 10 - app/src/Factory/DiactorosRequestFactory.php | 28 - .../Factory/FileSystemSettingsProvider.php | 22 - app/src/Factory/PipelineProvider.php | 25 - app/src/Factory/RequestFactory.php | 11 - app/src/Factory/SettingsContainerProvider.php | 26 - app/src/Factory/SettingsProvider.php | 10 - app/src/Http/BasicEmitter.php | 38 - app/src/Http/ContainerPipeline.php | 82 - app/src/Http/Emitter.php | 10 - app/src/Http/InvokerRoutedHandler.php | 34 - app/src/Http/Pipeline.php | 11 - app/src/Http/RouteMiddleware.php | 69 - app/src/Http/RoutedRequestHandler.php | 10 - app/src/Kernel.php | 32 - app/src/Middleware/Cache.php | 38 - app/src/Model/MarkdownPage.php | 13 - app/src/Repository/CachedMarkdownPageRepo.php | 49 - .../Repository/FileSystemMarkdownPageRepo.php | 61 - app/src/Repository/MarkdownPageRepo.php | 15 - app/src/Service/Cache/ApcuCache.php | 21 - app/src/Service/Cache/EasyCache.php | 9 - app/src/Service/Time/Now.php | 10 - app/src/Service/Time/SystemClockNow.php | 13 - app/src/Settings.php | 16 - app/src/Template/MarkdownParser.php | 8 - app/src/Template/MustacheRenderer.php | 17 - app/src/Template/ParsedownParser.php | 17 - app/src/Template/Renderer.php | 11 - app/templates/hello.html | 6 - app/templates/page.html | 5 - app/templates/page/list.html | 19 - app/templates/page/show.html | 17 - app/templates/pagelist.html | 11 - app/templates/partials/foot.html | 3 - app/templates/partials/head.html | 11 - .../01-front-controller/public/index.php | 5 - .../01-front-controller/src/Bootstrap.php | 3 - implementation/02-composer/composer.json | 17 - implementation/02-composer/composer.lock | 20 - implementation/02-composer/public/index.php | 5 - implementation/02-composer/src/Bootstrap.php | 3 - implementation/03-error-handler/composer.json | 22 - implementation/03-error-handler/composer.lock | 202 - .../03-error-handler/public/index.php | 5 - .../03-error-handler/src/Bootstrap.php | 25 - .../04-dev-helpers/.php-cs-fixer.php | 41 - implementation/04-dev-helpers/composer.json | 29 - implementation/04-dev-helpers/composer.lock | 420 -- implementation/04-dev-helpers/phpstan.neon | 4 - .../04-dev-helpers/public/index.php | 5 - .../04-dev-helpers/src/Bootstrap.php | 30 - implementation/05-http/.php-cs-fixer.php | 41 - implementation/05-http/composer.json | 30 - implementation/05-http/composer.lock | 627 --- implementation/05-http/phpstan-baseline.neon | 0 implementation/05-http/phpstan.neon | 4 - implementation/05-http/public/index.php | 5 - implementation/05-http/src/Bootstrap.php | 54 - implementation/06-router/.php-cs-fixer.php | 44 - implementation/06-router/composer.json | 31 - implementation/06-router/composer.lock | 677 --- implementation/06-router/config/routes.php | 17 - .../06-router/phpstan-baseline.neon | 0 implementation/06-router/phpstan.neon | 4 - implementation/06-router/public/index.php | 5 - implementation/06-router/src/Bootstrap.php | 89 - .../07-dispatching-to-class/.php-cs-fixer.php | 44 - .../07-dispatching-to-class/composer.json | 32 - .../07-dispatching-to-class/composer.lock | 734 --- .../07-dispatching-to-class/config/routes.php | 8 - .../phpstan-baseline.neon | 0 .../07-dispatching-to-class/phpstan.neon | 4 - .../07-dispatching-to-class/public/index.php | 5 - .../src/Action/Another.php | 20 - .../src/Action/Hello.php | 21 - .../src/Action/InternalServerError.php | 20 - .../src/Action/NotAllowed.php | 20 - .../src/Action/NotFound.php | 20 - .../07-dispatching-to-class/src/Bootstrap.php | 102 - .../InternalServerErrorException.php | 11 - .../src/Exception/NotAllowedException.php | 11 - .../src/Exception/NotFoundException.php | 11 - .../08-inversion-of-control/.php-cs-fixer.php | 44 - .../08-inversion-of-control/composer.json | 32 - .../08-inversion-of-control/composer.lock | 734 --- .../08-inversion-of-control/config/routes.php | 8 - .../phpstan-baseline.neon | 0 .../08-inversion-of-control/phpstan.neon | 4 - .../08-inversion-of-control/public/index.php | 5 - .../src/Action/Action.php | 17 - .../src/Action/Another.php | 19 - .../src/Action/Hello.php | 20 - .../src/Action/InternalServerError.php | 19 - .../src/Action/NotAllowed.php | 19 - .../src/Action/NotFound.php | 19 - .../08-inversion-of-control/src/Bootstrap.php | 102 - .../InternalServerErrorException.php | 11 - .../src/Exception/NotAllowedException.php | 11 - .../src/Exception/NotFoundException.php | 11 - .../09-dependency-injector/.php-cs-fixer.php | 44 - .../09-dependency-injector/composer.json | 34 - .../09-dependency-injector/composer.lock | 1020 ---- .../config/dependencies.php | 11 - .../09-dependency-injector/config/routes.php | 8 - .../phpstan-baseline.neon | 7 - .../09-dependency-injector/phpstan.neon | 7 - .../09-dependency-injector/public/index.php | 5 - .../src/Action/Action.php | 18 - .../src/Action/Another.php | 19 - .../src/Action/Hello.php | 20 - .../src/Action/InternalServerError.php | 19 - .../src/Action/NotAllowed.php | 19 - .../src/Action/NotFound.php | 19 - .../09-dependency-injector/src/Bootstrap.php | 106 - .../InternalServerErrorException.php | 11 - .../src/Exception/NotAllowedException.php | 11 - .../src/Exception/NotFoundException.php | 11 - implementation/10-invoker/.php-cs-fixer.php | 47 - implementation/10-invoker/composer.json | 32 - implementation/10-invoker/composer.lock | 1020 ---- .../10-invoker/config/dependencies.php | 12 - implementation/10-invoker/config/routes.php | 18 - implementation/10-invoker/phpstan.neon | 5 - implementation/10-invoker/public/favicon.ico | Bin 15086 -> 0 bytes implementation/10-invoker/public/index.php | 6 - implementation/10-invoker/src/.gitkeep | 0 .../10-invoker/src/Action/Hello.php | 23 - .../10-invoker/src/Action/Other.php | 20 - implementation/10-invoker/src/Bootstrap.php | 110 - .../src/Exception/InternalServerError.php | 11 - .../src/Exception/MethodNotAllowed.php | 11 - .../10-invoker/src/Exception/NotFound.php | 11 - .../10-invoker/src/Service/Time/Now.php | 12 - .../src/Service/Time/SystemClockNow.php | 15 - .../11-templating/.php-cs-fixer.php | 38 - implementation/11-templating/.phpcs.xml.dist | 9 - implementation/11-templating/composer.json | 46 - implementation/11-templating/composer.lock | 1550 ------ .../11-templating/config/dependencies.php | 32 - .../11-templating/config/routes.php | 12 - .../11-templating/config/settings.php | 9 - .../11-templating/phpstan-baseline.neon | 7 - implementation/11-templating/phpstan.neon | 8 - .../11-templating/public/favicon.ico | Bin 15086 -> 0 bytes implementation/11-templating/public/index.php | 5 - implementation/11-templating/src/.gitkeep | 0 .../11-templating/src/Action/Hello.php | 31 - .../11-templating/src/Action/Other.php | 19 - .../11-templating/src/Bootstrap.php | 110 - .../src/Exception/InternalServerError.php | 9 - .../src/Exception/MethodNotAllowed.php | 9 - .../11-templating/src/Exception/NotFound.php | 9 - .../11-templating/src/Service/Time/Now.php | 10 - .../src/Service/Time/SystemClockNow.php | 13 - implementation/11-templating/src/Settings.php | 13 - .../src/Template/MustacheRenderer.php | 17 - .../11-templating/src/Template/Renderer.php | 11 - .../11-templating/templates/hello.html | 11 - .../12-configuration/.php-cs-fixer.php | 38 - .../12-configuration/.phpcs.xml.dist | 9 - implementation/12-configuration/composer.json | 46 - implementation/12-configuration/composer.lock | 1550 ------ .../12-configuration/config/dependencies.php | 22 - .../12-configuration/config/routes.php | 12 - .../12-configuration/config/settings.php | 10 - .../12-configuration/phpstan-baseline.neon | 0 implementation/12-configuration/phpstan.neon | 8 - .../12-configuration/public/favicon.ico | Bin 15086 -> 0 bytes .../12-configuration/public/index.php | 5 - implementation/12-configuration/src/.gitkeep | 0 .../12-configuration/src/Action/Hello.php | 31 - .../12-configuration/src/Action/Other.php | 19 - .../12-configuration/src/Bootstrap.php | 111 - .../src/Exception/InternalServerError.php | 9 - .../src/Exception/MethodNotAllowed.php | 9 - .../src/Exception/NotFound.php | 9 - .../src/Factory/ContainerProvider.php | 10 - .../Factory/FileSystemSettingsProvider.php | 18 - .../src/Factory/SettingsContainerProvider.php | 25 - .../src/Factory/SettingsProvider.php | 10 - .../12-configuration/src/Service/Time/Now.php | 10 - .../src/Service/Time/SystemClockNow.php | 13 - .../12-configuration/src/Settings.php | 14 - .../src/Template/MustacheRenderer.php | 17 - .../src/Template/Renderer.php | 11 - .../12-configuration/templates/hello.html | 11 - .../13-refactoring/.php-cs-fixer.php | 38 - implementation/13-refactoring/.phpcs.xml.dist | 9 - implementation/13-refactoring/composer.json | 47 - implementation/13-refactoring/composer.lock | 1607 ------- .../13-refactoring/config/dependencies.php | 39 - .../13-refactoring/config/routes.php | 12 - .../13-refactoring/config/settings.php | 10 - .../13-refactoring/phpstan-baseline.neon | 7 - implementation/13-refactoring/phpstan.neon | 8 - .../13-refactoring/public/favicon.ico | Bin 15086 -> 0 bytes .../13-refactoring/public/index.php | 5 - implementation/13-refactoring/src/.gitkeep | 0 .../13-refactoring/src/Action/Hello.php | 31 - .../13-refactoring/src/Action/Other.php | 19 - .../13-refactoring/src/Bootstrap.php | 40 - .../src/Exception/InternalServerError.php | 9 - .../src/Exception/MethodNotAllowed.php | 9 - .../13-refactoring/src/Exception/NotFound.php | 9 - .../src/Factory/ContainerProvider.php | 10 - .../src/Factory/DiactorosRequestFactory.php | 23 - .../Factory/FileSystemSettingsProvider.php | 18 - .../src/Factory/RequestFactory.php | 11 - .../src/Factory/SettingsContainerProvider.php | 25 - .../src/Factory/SettingsProvider.php | 10 - .../13-refactoring/src/Http/BasicEmitter.php | 38 - .../13-refactoring/src/Http/Emitter.php | 10 - .../src/Http/InvokerRoutedHandler.php | 37 - .../src/Http/RouteMiddleware.php | 69 - .../src/Http/RoutedRequestHandler.php | 10 - implementation/13-refactoring/src/Kernel.php | 34 - .../13-refactoring/src/Service/Time/Now.php | 10 - .../src/Service/Time/SystemClockNow.php | 13 - .../13-refactoring/src/Settings.php | 14 - .../src/Template/MustacheRenderer.php | 17 - .../13-refactoring/src/Template/Renderer.php | 11 - .../13-refactoring/templates/hello.html | 11 - .../14-middleware/.php-cs-fixer.php | 38 - implementation/14-middleware/.phpcs.xml.dist | 9 - implementation/14-middleware/composer.json | 50 - implementation/14-middleware/composer.lock | 1815 ------- .../14-middleware/config/dependencies.php | 42 - .../14-middleware/config/middlewares.php | 11 - .../14-middleware/config/routes.php | 12 - .../14-middleware/config/settings.php | 11 - .../14-middleware/phpstan-baseline.neon | 7 - implementation/14-middleware/phpstan.neon | 8 - .../14-middleware/public/favicon.ico | Bin 15086 -> 0 bytes implementation/14-middleware/public/index.php | 5 - implementation/14-middleware/src/.gitkeep | 0 .../14-middleware/src/Action/Hello.php | 31 - .../14-middleware/src/Action/Other.php | 19 - .../14-middleware/src/Bootstrap.php | 40 - .../src/Exception/InternalServerError.php | 9 - .../src/Exception/MethodNotAllowed.php | 9 - .../14-middleware/src/Exception/NotFound.php | 9 - .../src/Factory/ContainerProvider.php | 10 - .../src/Factory/DiactorosRequestFactory.php | 28 - .../Factory/FileSystemSettingsProvider.php | 22 - .../src/Factory/PipelineProvider.php | 25 - .../src/Factory/RequestFactory.php | 11 - .../src/Factory/SettingsContainerProvider.php | 25 - .../src/Factory/SettingsProvider.php | 10 - .../14-middleware/src/Http/BasicEmitter.php | 38 - .../src/Http/ContainerPipeline.php | 82 - .../14-middleware/src/Http/Emitter.php | 10 - .../src/Http/InvokerRoutedHandler.php | 34 - .../14-middleware/src/Http/Pipeline.php | 11 - .../src/Http/RouteMiddleware.php | 69 - .../src/Http/RoutedRequestHandler.php | 10 - implementation/14-middleware/src/Kernel.php | 32 - .../14-middleware/src/Service/Time/Now.php | 10 - .../src/Service/Time/SystemClockNow.php | 13 - implementation/14-middleware/src/Settings.php | 15 - .../src/Template/MustacheRenderer.php | 17 - .../14-middleware/src/Template/Renderer.php | 11 - .../14-middleware/templates/hello.html | 11 - .../15-adding-content/.php-cs-fixer.php | 38 - .../15-adding-content/.phpcs.xml.dist | 9 - .../15-adding-content/cli-config.php | 13 - .../15-adding-content/composer.json | 56 - .../15-adding-content/composer.lock | 4246 ----------------- .../15-adding-content/config/dependencies.php | 60 - .../15-adding-content/config/middlewares.php | 11 - .../15-adding-content/config/routes.php | 15 - .../15-adding-content/config/settings.php | 23 - .../data/pages/01-front-controller.md | 53 - .../data/pages/02-composer.md | 75 - .../data/pages/03-error-handler.md | 79 - .../data/pages/04-development-helpers.md | 260 - .../15-adding-content/data/pages/05-http.md | 124 - .../15-adding-content/data/pages/06-router.md | 101 - .../data/pages/07-dispatching-to-a-class.md | 137 - .../data/pages/08-inversion-of-control.md | 54 - .../data/pages/09-dependency-injector.md | 213 - .../data/pages/10-invoker.md | 102 - .../data/pages/11-templating.md | 240 - .../data/pages/12-configuration.md | 201 - .../data/pages/13-refactoring.md | 377 -- .../data/pages/14-middleware.md | 298 -- .../15-adding-content/phpstan-baseline.neon | 7 - implementation/15-adding-content/phpstan.neon | 8 - .../public/css/spectre-exp.min.css | 1 - .../public/css/spectre-icons.min.css | 1 - .../public/css/spectre.min.css | 1 - .../15-adding-content/public/favicon.ico | Bin 15086 -> 0 bytes .../15-adding-content/public/index.php | 5 - implementation/15-adding-content/src/.gitkeep | 0 .../15-adding-content/src/Action/Hello.php | 31 - .../15-adding-content/src/Action/Other.php | 16 - .../15-adding-content/src/Action/Page.php | 80 - .../15-adding-content/src/Bootstrap.php | 40 - .../src/Exception/InternalServerError.php | 9 - .../src/Exception/MethodNotAllowed.php | 9 - .../src/Exception/NotFound.php | 9 - .../src/Factory/ContainerProvider.php | 10 - .../src/Factory/DiactorosRequestFactory.php | 28 - .../src/Factory/DoctrineEm.php | 32 - .../Factory/FileSystemSettingsProvider.php | 22 - .../src/Factory/PipelineProvider.php | 25 - .../src/Factory/RequestFactory.php | 11 - .../src/Factory/SettingsContainerProvider.php | 26 - .../src/Factory/SettingsProvider.php | 10 - .../src/Http/BasicEmitter.php | 38 - .../src/Http/ContainerPipeline.php | 82 - .../15-adding-content/src/Http/Emitter.php | 10 - .../src/Http/InvokerRoutedHandler.php | 34 - .../15-adding-content/src/Http/Pipeline.php | 11 - .../src/Http/RouteMiddleware.php | 69 - .../src/Http/RoutedRequestHandler.php | 10 - .../15-adding-content/src/Kernel.php | 32 - .../src/Middleware/CacheMiddleware.php | 40 - .../src/Model/MarkdownPage.php | 21 - .../src/Repository/CachedMarkdownPageRepo.php | 62 - .../Repository/DoctrineMarkdownPageRepo.php | 59 - .../src/Repository/MarkdownPageFilesystem.php | 69 - .../src/Repository/MarkdownPageRepo.php | 19 - .../src/Service/Time/Now.php | 10 - .../src/Service/Time/SystemClockNow.php | 13 - .../15-adding-content/src/Settings.php | 33 - .../src/Template/GithubMarkdownRenderer.php | 28 - .../src/Template/MarkdownParser.php | 8 - .../src/Template/MustacheRenderer.php | 17 - .../src/Template/ParsedownParser.php | 17 - .../src/Template/Renderer.php | 11 - .../15-adding-content/templates/hello.html | 6 - .../15-adding-content/templates/page.html | 5 - .../templates/page/list.html | 19 - .../templates/page/show.html | 17 - .../15-adding-content/templates/pagelist.html | 11 - .../templates/partials/foot.html | 3 - .../templates/partials/head.html | 11 - implementation/16-caching/.php-cs-fixer.php | 38 - implementation/16-caching/.phpcs.xml.dist | 9 - implementation/16-caching/composer.json | 54 - implementation/16-caching/composer.lock | 2273 --------- .../16-caching/config/dependencies.php | 54 - .../16-caching/config/middlewares.php | 13 - implementation/16-caching/config/routes.php | 14 - implementation/16-caching/config/settings.php | 12 - .../data/pages/01-front-controller.md | 53 - .../16-caching/data/pages/02-composer.md | 75 - .../16-caching/data/pages/03-error-handler.md | 79 - .../data/pages/04-development-helpers.md | 260 - .../16-caching/data/pages/05-http.md | 124 - .../16-caching/data/pages/06-router.md | 101 - .../data/pages/07-dispatching-to-a-class.md | 137 - .../data/pages/08-inversion-of-control.md | 54 - .../data/pages/09-dependency-injector.md | 213 - .../16-caching/data/pages/10-invoker.md | 102 - .../16-caching/data/pages/11-templating.md | 240 - .../16-caching/data/pages/12-configuration.md | 201 - .../16-caching/data/pages/13-refactoring.md | 377 -- .../16-caching/data/pages/14-middleware.md | 298 -- .../16-caching/phpstan-baseline.neon | 7 - implementation/16-caching/phpstan.neon | 8 - .../16-caching/public/css/spectre-exp.min.css | 1 - .../public/css/spectre-icons.min.css | 1 - .../16-caching/public/css/spectre.min.css | 1 - implementation/16-caching/public/favicon.ico | Bin 15086 -> 0 bytes implementation/16-caching/public/index.php | 5 - implementation/16-caching/src/.gitkeep | 0 .../16-caching/src/Action/Hello.php | 31 - .../16-caching/src/Action/Other.php | 19 - implementation/16-caching/src/Action/Page.php | 35 - implementation/16-caching/src/Bootstrap.php | 40 - .../src/Exception/InternalServerError.php | 9 - .../src/Exception/MethodNotAllowed.php | 9 - .../16-caching/src/Exception/NotFound.php | 9 - .../src/Factory/ContainerProvider.php | 10 - .../src/Factory/DiactorosRequestFactory.php | 28 - .../Factory/FileSystemSettingsProvider.php | 22 - .../src/Factory/PipelineProvider.php | 25 - .../16-caching/src/Factory/RequestFactory.php | 11 - .../src/Factory/SettingsContainerProvider.php | 25 - .../src/Factory/SettingsProvider.php | 10 - .../16-caching/src/Http/BasicEmitter.php | 38 - .../16-caching/src/Http/ContainerPipeline.php | 82 - .../16-caching/src/Http/Emitter.php | 10 - .../src/Http/InvokerRoutedHandler.php | 34 - .../16-caching/src/Http/Pipeline.php | 11 - .../16-caching/src/Http/RouteMiddleware.php | 69 - .../src/Http/RoutedRequestHandler.php | 10 - implementation/16-caching/src/Kernel.php | 32 - .../src/Middleware/CacheMiddleware.php | 36 - .../16-caching/src/Model/MarkdownPage.php | 13 - .../src/Repository/CachedMarkdownPageRepo.php | 46 - .../src/Repository/MarkdownPageFilesystem.php | 63 - .../src/Repository/MarkdownPageRepo.php | 17 - .../16-caching/src/Service/Time/Now.php | 10 - .../src/Service/Time/SystemClockNow.php | 13 - implementation/16-caching/src/Settings.php | 16 - .../src/Template/MustacheRenderer.php | 17 - .../16-caching/src/Template/Renderer.php | 11 - .../16-caching/templates/hello.html | 6 - implementation/16-caching/templates/page.html | 5 - .../16-caching/templates/partials/foot.html | 3 - .../16-caching/templates/partials/head.html | 11 - .../16-data-repository/.php-cs-fixer.php | 38 - .../16-data-repository/.phpcs.xml.dist | 9 - .../16-data-repository/cli-config.php | 13 - .../16-data-repository/composer.json | 54 - .../16-data-repository/composer.lock | 2438 ---------- .../config/dependencies.php | 55 - .../16-data-repository/config/middlewares.php | 11 - .../16-data-repository/config/routes.php | 15 - .../16-data-repository/config/settings.php | 12 - .../data/pages/01-front-controller.md | 53 - .../data/pages/02-composer.md | 75 - .../data/pages/03-error-handler.md | 79 - .../data/pages/04-development-helpers.md | 260 - .../16-data-repository/data/pages/05-http.md | 124 - .../data/pages/06-router.md | 101 - .../data/pages/07-dispatching-to-a-class.md | 137 - .../data/pages/08-inversion-of-control.md | 54 - .../data/pages/09-dependency-injector.md | 213 - .../data/pages/10-invoker.md | 102 - .../data/pages/11-templating.md | 236 - .../data/pages/12-configuration.md | 201 - .../data/pages/13-refactoring.md | 377 -- .../data/pages/14-middleware.md | 298 -- .../16-data-repository/phpstan-baseline.neon | 7 - .../16-data-repository/phpstan.neon | 8 - .../public/css/spectre-exp.min.css | 1 - .../public/css/spectre-icons.min.css | 1 - .../public/css/spectre.min.css | 1 - .../16-data-repository/public/favicon.ico | Bin 15086 -> 0 bytes .../16-data-repository/public/index.php | 5 - .../16-data-repository/src/.gitkeep | 0 .../16-data-repository/src/Action/Hello.php | 31 - .../16-data-repository/src/Action/Other.php | 16 - .../16-data-repository/src/Action/Page.php | 60 - .../16-data-repository/src/Bootstrap.php | 40 - .../src/Exception/InternalServerError.php | 9 - .../src/Exception/MethodNotAllowed.php | 9 - .../src/Exception/NotFound.php | 9 - .../src/Factory/ContainerProvider.php | 10 - .../src/Factory/DiactorosRequestFactory.php | 28 - .../src/Factory/DoctrineEm.php | 32 - .../Factory/FileSystemSettingsProvider.php | 22 - .../src/Factory/PipelineProvider.php | 25 - .../src/Factory/RequestFactory.php | 11 - .../src/Factory/SettingsContainerProvider.php | 26 - .../src/Factory/SettingsProvider.php | 10 - .../src/Http/BasicEmitter.php | 38 - .../src/Http/ContainerPipeline.php | 82 - .../16-data-repository/src/Http/Emitter.php | 10 - .../src/Http/InvokerRoutedHandler.php | 34 - .../16-data-repository/src/Http/Pipeline.php | 11 - .../src/Http/RouteMiddleware.php | 69 - .../src/Http/RoutedRequestHandler.php | 10 - .../16-data-repository/src/Kernel.php | 32 - .../src/Model/MarkdownPage.php | 13 - .../Repository/FileSystemMarkdownPageRepo.php | 61 - .../src/Repository/MarkdownPageRepo.php | 15 - .../src/Service/Time/Now.php | 10 - .../src/Service/Time/SystemClockNow.php | 13 - .../16-data-repository/src/Settings.php | 16 - .../src/Template/MarkdownParser.php | 8 - .../src/Template/MustacheRenderer.php | 17 - .../src/Template/ParsedownParser.php | 17 - .../src/Template/Renderer.php | 11 - .../16-data-repository/templates/hello.html | 6 - .../16-data-repository/templates/page.html | 5 - .../templates/page/list.html | 19 - .../templates/page/show.html | 17 - .../templates/pagelist.html | 11 - .../templates/partials/foot.html | 3 - .../templates/partials/head.html | 11 - implementation/18-caching/.php-cs-fixer.php | 38 - implementation/18-caching/.phpcs.xml.dist | 9 - implementation/18-caching/cli-config.php | 13 - implementation/18-caching/composer.json | 57 - implementation/18-caching/composer.lock | 2440 ---------- .../18-caching/config/dependencies.php | 58 - .../18-caching/config/middlewares.php | 13 - implementation/18-caching/config/routes.php | 15 - implementation/18-caching/config/settings.php | 12 - .../data/pages/01-front-controller.md | 53 - .../18-caching/data/pages/02-composer.md | 75 - .../18-caching/data/pages/03-error-handler.md | 79 - .../data/pages/04-development-helpers.md | 260 - .../18-caching/data/pages/05-http.md | 124 - .../18-caching/data/pages/06-router.md | 101 - .../data/pages/07-dispatching-to-a-class.md | 137 - .../data/pages/08-inversion-of-control.md | 54 - .../data/pages/09-dependency-injector.md | 213 - .../18-caching/data/pages/10-invoker.md | 102 - .../18-caching/data/pages/11-templating.md | 236 - .../18-caching/data/pages/12-configuration.md | 200 - .../18-caching/data/pages/13-refactoring.md | 373 -- .../18-caching/data/pages/14-middleware.md | 303 -- .../data/pages/15-adding-content.md | 253 - .../data/pages/16-data-repository.md | 265 - .../18-caching/data/pages/17-performance.md | 43 - .../18-caching/data/pages/18-caching.md | 252 - .../18-caching/phpstan-baseline.neon | 7 - implementation/18-caching/phpstan.neon | 8 - .../18-caching/public/css/spectre-exp.min.css | 1 - .../public/css/spectre-icons.min.css | 1 - .../18-caching/public/css/spectre.min.css | 1 - implementation/18-caching/public/favicon.ico | Bin 15086 -> 0 bytes implementation/18-caching/public/index.php | 5 - implementation/18-caching/src/.gitkeep | 0 .../18-caching/src/Action/Hello.php | 31 - .../18-caching/src/Action/Other.php | 16 - implementation/18-caching/src/Action/Page.php | 60 - implementation/18-caching/src/Bootstrap.php | 40 - .../src/Exception/InternalServerError.php | 9 - .../src/Exception/MethodNotAllowed.php | 9 - .../18-caching/src/Exception/NotFound.php | 9 - .../src/Factory/ContainerProvider.php | 10 - .../src/Factory/DiactorosRequestFactory.php | 28 - .../Factory/FileSystemSettingsProvider.php | 22 - .../src/Factory/PipelineProvider.php | 25 - .../18-caching/src/Factory/RequestFactory.php | 11 - .../src/Factory/SettingsContainerProvider.php | 26 - .../src/Factory/SettingsProvider.php | 10 - .../18-caching/src/Http/BasicEmitter.php | 38 - .../18-caching/src/Http/ContainerPipeline.php | 82 - .../18-caching/src/Http/Emitter.php | 10 - .../src/Http/InvokerRoutedHandler.php | 34 - .../18-caching/src/Http/Pipeline.php | 11 - .../18-caching/src/Http/RouteMiddleware.php | 69 - .../src/Http/RoutedRequestHandler.php | 10 - implementation/18-caching/src/Kernel.php | 32 - .../18-caching/src/Middleware/Cache.php | 38 - .../18-caching/src/Model/MarkdownPage.php | 13 - .../src/Repository/CachedMarkdownPageRepo.php | 49 - .../Repository/FileSystemMarkdownPageRepo.php | 61 - .../src/Repository/MarkdownPageRepo.php | 15 - .../src/Service/Cache/ApcuCache.php | 21 - .../src/Service/Cache/EasyCache.php | 9 - .../18-caching/src/Service/Time/Now.php | 10 - .../src/Service/Time/SystemClockNow.php | 13 - implementation/18-caching/src/Settings.php | 16 - .../src/Template/MarkdownParser.php | 8 - .../src/Template/MustacheRenderer.php | 17 - .../src/Template/ParsedownParser.php | 17 - .../18-caching/src/Template/Renderer.php | 11 - .../18-caching/templates/hello.html | 6 - implementation/18-caching/templates/page.html | 5 - .../18-caching/templates/page/list.html | 19 - .../18-caching/templates/page/show.html | 17 - .../18-caching/templates/pagelist.html | 11 - .../18-caching/templates/partials/foot.html | 3 - .../18-caching/templates/partials/head.html | 11 - to-be-continued.md | 17 - 595 files changed, 49207 deletions(-) delete mode 100644 app/.php-cs-fixer.php delete mode 100644 app/.phpcs.xml.dist delete mode 100644 app/cli-config.php delete mode 100644 app/composer.json delete mode 100644 app/composer.lock delete mode 100644 app/config/dependencies.php delete mode 100644 app/config/middlewares.php delete mode 100644 app/config/routes.php delete mode 100644 app/config/settings.php delete mode 100644 app/data/pages/01-front-controller.md delete mode 100644 app/data/pages/02-composer.md delete mode 100644 app/data/pages/03-error-handler.md delete mode 100644 app/data/pages/04-development-helpers.md delete mode 100644 app/data/pages/05-http.md delete mode 100644 app/data/pages/06-router.md delete mode 100644 app/data/pages/07-dispatching-to-a-class.md delete mode 100644 app/data/pages/08-inversion-of-control.md delete mode 100644 app/data/pages/09-dependency-injector.md delete mode 100644 app/data/pages/10-invoker.md delete mode 100644 app/data/pages/11-templating.md delete mode 100644 app/data/pages/12-configuration.md delete mode 100644 app/data/pages/13-refactoring.md delete mode 100644 app/data/pages/14-middleware.md delete mode 100644 app/data/pages/15-adding-content.md delete mode 100644 app/data/pages/16-data-repository.md delete mode 100644 app/data/pages/17-performance.md delete mode 100644 app/data/pages/18-caching.md delete mode 100644 app/phpstan-baseline.neon delete mode 100644 app/phpstan.neon delete mode 100644 app/public/css/spectre-exp.min.css delete mode 100644 app/public/css/spectre-icons.min.css delete mode 100644 app/public/css/spectre.min.css delete mode 100644 app/public/index.php delete mode 100644 app/src/.gitkeep delete mode 100644 app/src/Action/Hello.php delete mode 100644 app/src/Action/Other.php delete mode 100644 app/src/Action/Page.php delete mode 100644 app/src/Bootstrap.php delete mode 100644 app/src/Exception/InternalServerError.php delete mode 100644 app/src/Exception/MethodNotAllowed.php delete mode 100644 app/src/Exception/NotFound.php delete mode 100644 app/src/Factory/ContainerProvider.php delete mode 100644 app/src/Factory/DiactorosRequestFactory.php delete mode 100644 app/src/Factory/FileSystemSettingsProvider.php delete mode 100644 app/src/Factory/PipelineProvider.php delete mode 100644 app/src/Factory/RequestFactory.php delete mode 100644 app/src/Factory/SettingsContainerProvider.php delete mode 100644 app/src/Factory/SettingsProvider.php delete mode 100644 app/src/Http/BasicEmitter.php delete mode 100644 app/src/Http/ContainerPipeline.php delete mode 100644 app/src/Http/Emitter.php delete mode 100644 app/src/Http/InvokerRoutedHandler.php delete mode 100644 app/src/Http/Pipeline.php delete mode 100644 app/src/Http/RouteMiddleware.php delete mode 100644 app/src/Http/RoutedRequestHandler.php delete mode 100644 app/src/Kernel.php delete mode 100644 app/src/Middleware/Cache.php delete mode 100644 app/src/Model/MarkdownPage.php delete mode 100644 app/src/Repository/CachedMarkdownPageRepo.php delete mode 100644 app/src/Repository/FileSystemMarkdownPageRepo.php delete mode 100644 app/src/Repository/MarkdownPageRepo.php delete mode 100644 app/src/Service/Cache/ApcuCache.php delete mode 100644 app/src/Service/Cache/EasyCache.php delete mode 100644 app/src/Service/Time/Now.php delete mode 100644 app/src/Service/Time/SystemClockNow.php delete mode 100644 app/src/Settings.php delete mode 100644 app/src/Template/MarkdownParser.php delete mode 100644 app/src/Template/MustacheRenderer.php delete mode 100644 app/src/Template/ParsedownParser.php delete mode 100644 app/src/Template/Renderer.php delete mode 100644 app/templates/hello.html delete mode 100644 app/templates/page.html delete mode 100644 app/templates/page/list.html delete mode 100644 app/templates/page/show.html delete mode 100644 app/templates/pagelist.html delete mode 100644 app/templates/partials/foot.html delete mode 100644 app/templates/partials/head.html delete mode 100644 implementation/01-front-controller/public/index.php delete mode 100644 implementation/01-front-controller/src/Bootstrap.php delete mode 100644 implementation/02-composer/composer.json delete mode 100644 implementation/02-composer/composer.lock delete mode 100644 implementation/02-composer/public/index.php delete mode 100644 implementation/02-composer/src/Bootstrap.php delete mode 100644 implementation/03-error-handler/composer.json delete mode 100644 implementation/03-error-handler/composer.lock delete mode 100644 implementation/03-error-handler/public/index.php delete mode 100644 implementation/03-error-handler/src/Bootstrap.php delete mode 100644 implementation/04-dev-helpers/.php-cs-fixer.php delete mode 100644 implementation/04-dev-helpers/composer.json delete mode 100644 implementation/04-dev-helpers/composer.lock delete mode 100644 implementation/04-dev-helpers/phpstan.neon delete mode 100644 implementation/04-dev-helpers/public/index.php delete mode 100644 implementation/04-dev-helpers/src/Bootstrap.php delete mode 100644 implementation/05-http/.php-cs-fixer.php delete mode 100644 implementation/05-http/composer.json delete mode 100644 implementation/05-http/composer.lock delete mode 100644 implementation/05-http/phpstan-baseline.neon delete mode 100644 implementation/05-http/phpstan.neon delete mode 100644 implementation/05-http/public/index.php delete mode 100644 implementation/05-http/src/Bootstrap.php delete mode 100644 implementation/06-router/.php-cs-fixer.php delete mode 100644 implementation/06-router/composer.json delete mode 100644 implementation/06-router/composer.lock delete mode 100644 implementation/06-router/config/routes.php delete mode 100644 implementation/06-router/phpstan-baseline.neon delete mode 100644 implementation/06-router/phpstan.neon delete mode 100644 implementation/06-router/public/index.php delete mode 100644 implementation/06-router/src/Bootstrap.php delete mode 100644 implementation/07-dispatching-to-class/.php-cs-fixer.php delete mode 100644 implementation/07-dispatching-to-class/composer.json delete mode 100644 implementation/07-dispatching-to-class/composer.lock delete mode 100644 implementation/07-dispatching-to-class/config/routes.php delete mode 100644 implementation/07-dispatching-to-class/phpstan-baseline.neon delete mode 100644 implementation/07-dispatching-to-class/phpstan.neon delete mode 100644 implementation/07-dispatching-to-class/public/index.php delete mode 100644 implementation/07-dispatching-to-class/src/Action/Another.php delete mode 100644 implementation/07-dispatching-to-class/src/Action/Hello.php delete mode 100644 implementation/07-dispatching-to-class/src/Action/InternalServerError.php delete mode 100644 implementation/07-dispatching-to-class/src/Action/NotAllowed.php delete mode 100644 implementation/07-dispatching-to-class/src/Action/NotFound.php delete mode 100644 implementation/07-dispatching-to-class/src/Bootstrap.php delete mode 100644 implementation/07-dispatching-to-class/src/Exception/InternalServerErrorException.php delete mode 100644 implementation/07-dispatching-to-class/src/Exception/NotAllowedException.php delete mode 100644 implementation/07-dispatching-to-class/src/Exception/NotFoundException.php delete mode 100644 implementation/08-inversion-of-control/.php-cs-fixer.php delete mode 100644 implementation/08-inversion-of-control/composer.json delete mode 100644 implementation/08-inversion-of-control/composer.lock delete mode 100644 implementation/08-inversion-of-control/config/routes.php delete mode 100644 implementation/08-inversion-of-control/phpstan-baseline.neon delete mode 100644 implementation/08-inversion-of-control/phpstan.neon delete mode 100644 implementation/08-inversion-of-control/public/index.php delete mode 100644 implementation/08-inversion-of-control/src/Action/Action.php delete mode 100644 implementation/08-inversion-of-control/src/Action/Another.php delete mode 100644 implementation/08-inversion-of-control/src/Action/Hello.php delete mode 100644 implementation/08-inversion-of-control/src/Action/InternalServerError.php delete mode 100644 implementation/08-inversion-of-control/src/Action/NotAllowed.php delete mode 100644 implementation/08-inversion-of-control/src/Action/NotFound.php delete mode 100644 implementation/08-inversion-of-control/src/Bootstrap.php delete mode 100644 implementation/08-inversion-of-control/src/Exception/InternalServerErrorException.php delete mode 100644 implementation/08-inversion-of-control/src/Exception/NotAllowedException.php delete mode 100644 implementation/08-inversion-of-control/src/Exception/NotFoundException.php delete mode 100644 implementation/09-dependency-injector/.php-cs-fixer.php delete mode 100644 implementation/09-dependency-injector/composer.json delete mode 100644 implementation/09-dependency-injector/composer.lock delete mode 100644 implementation/09-dependency-injector/config/dependencies.php delete mode 100644 implementation/09-dependency-injector/config/routes.php delete mode 100644 implementation/09-dependency-injector/phpstan-baseline.neon delete mode 100644 implementation/09-dependency-injector/phpstan.neon delete mode 100644 implementation/09-dependency-injector/public/index.php delete mode 100644 implementation/09-dependency-injector/src/Action/Action.php delete mode 100644 implementation/09-dependency-injector/src/Action/Another.php delete mode 100644 implementation/09-dependency-injector/src/Action/Hello.php delete mode 100644 implementation/09-dependency-injector/src/Action/InternalServerError.php delete mode 100644 implementation/09-dependency-injector/src/Action/NotAllowed.php delete mode 100644 implementation/09-dependency-injector/src/Action/NotFound.php delete mode 100644 implementation/09-dependency-injector/src/Bootstrap.php delete mode 100644 implementation/09-dependency-injector/src/Exception/InternalServerErrorException.php delete mode 100644 implementation/09-dependency-injector/src/Exception/NotAllowedException.php delete mode 100644 implementation/09-dependency-injector/src/Exception/NotFoundException.php delete mode 100644 implementation/10-invoker/.php-cs-fixer.php delete mode 100644 implementation/10-invoker/composer.json delete mode 100644 implementation/10-invoker/composer.lock delete mode 100644 implementation/10-invoker/config/dependencies.php delete mode 100644 implementation/10-invoker/config/routes.php delete mode 100644 implementation/10-invoker/phpstan.neon delete mode 100644 implementation/10-invoker/public/favicon.ico delete mode 100644 implementation/10-invoker/public/index.php delete mode 100644 implementation/10-invoker/src/.gitkeep delete mode 100644 implementation/10-invoker/src/Action/Hello.php delete mode 100644 implementation/10-invoker/src/Action/Other.php delete mode 100644 implementation/10-invoker/src/Bootstrap.php delete mode 100644 implementation/10-invoker/src/Exception/InternalServerError.php delete mode 100644 implementation/10-invoker/src/Exception/MethodNotAllowed.php delete mode 100644 implementation/10-invoker/src/Exception/NotFound.php delete mode 100644 implementation/10-invoker/src/Service/Time/Now.php delete mode 100644 implementation/10-invoker/src/Service/Time/SystemClockNow.php delete mode 100644 implementation/11-templating/.php-cs-fixer.php delete mode 100644 implementation/11-templating/.phpcs.xml.dist delete mode 100644 implementation/11-templating/composer.json delete mode 100644 implementation/11-templating/composer.lock delete mode 100644 implementation/11-templating/config/dependencies.php delete mode 100644 implementation/11-templating/config/routes.php delete mode 100644 implementation/11-templating/config/settings.php delete mode 100644 implementation/11-templating/phpstan-baseline.neon delete mode 100644 implementation/11-templating/phpstan.neon delete mode 100644 implementation/11-templating/public/favicon.ico delete mode 100644 implementation/11-templating/public/index.php delete mode 100644 implementation/11-templating/src/.gitkeep delete mode 100644 implementation/11-templating/src/Action/Hello.php delete mode 100644 implementation/11-templating/src/Action/Other.php delete mode 100644 implementation/11-templating/src/Bootstrap.php delete mode 100644 implementation/11-templating/src/Exception/InternalServerError.php delete mode 100644 implementation/11-templating/src/Exception/MethodNotAllowed.php delete mode 100644 implementation/11-templating/src/Exception/NotFound.php delete mode 100644 implementation/11-templating/src/Service/Time/Now.php delete mode 100644 implementation/11-templating/src/Service/Time/SystemClockNow.php delete mode 100644 implementation/11-templating/src/Settings.php delete mode 100644 implementation/11-templating/src/Template/MustacheRenderer.php delete mode 100644 implementation/11-templating/src/Template/Renderer.php delete mode 100644 implementation/11-templating/templates/hello.html delete mode 100644 implementation/12-configuration/.php-cs-fixer.php delete mode 100644 implementation/12-configuration/.phpcs.xml.dist delete mode 100644 implementation/12-configuration/composer.json delete mode 100644 implementation/12-configuration/composer.lock delete mode 100644 implementation/12-configuration/config/dependencies.php delete mode 100644 implementation/12-configuration/config/routes.php delete mode 100644 implementation/12-configuration/config/settings.php delete mode 100644 implementation/12-configuration/phpstan-baseline.neon delete mode 100644 implementation/12-configuration/phpstan.neon delete mode 100644 implementation/12-configuration/public/favicon.ico delete mode 100644 implementation/12-configuration/public/index.php delete mode 100644 implementation/12-configuration/src/.gitkeep delete mode 100644 implementation/12-configuration/src/Action/Hello.php delete mode 100644 implementation/12-configuration/src/Action/Other.php delete mode 100644 implementation/12-configuration/src/Bootstrap.php delete mode 100644 implementation/12-configuration/src/Exception/InternalServerError.php delete mode 100644 implementation/12-configuration/src/Exception/MethodNotAllowed.php delete mode 100644 implementation/12-configuration/src/Exception/NotFound.php delete mode 100644 implementation/12-configuration/src/Factory/ContainerProvider.php delete mode 100644 implementation/12-configuration/src/Factory/FileSystemSettingsProvider.php delete mode 100644 implementation/12-configuration/src/Factory/SettingsContainerProvider.php delete mode 100644 implementation/12-configuration/src/Factory/SettingsProvider.php delete mode 100644 implementation/12-configuration/src/Service/Time/Now.php delete mode 100644 implementation/12-configuration/src/Service/Time/SystemClockNow.php delete mode 100644 implementation/12-configuration/src/Settings.php delete mode 100644 implementation/12-configuration/src/Template/MustacheRenderer.php delete mode 100644 implementation/12-configuration/src/Template/Renderer.php delete mode 100644 implementation/12-configuration/templates/hello.html delete mode 100644 implementation/13-refactoring/.php-cs-fixer.php delete mode 100644 implementation/13-refactoring/.phpcs.xml.dist delete mode 100644 implementation/13-refactoring/composer.json delete mode 100644 implementation/13-refactoring/composer.lock delete mode 100644 implementation/13-refactoring/config/dependencies.php delete mode 100644 implementation/13-refactoring/config/routes.php delete mode 100644 implementation/13-refactoring/config/settings.php delete mode 100644 implementation/13-refactoring/phpstan-baseline.neon delete mode 100644 implementation/13-refactoring/phpstan.neon delete mode 100644 implementation/13-refactoring/public/favicon.ico delete mode 100644 implementation/13-refactoring/public/index.php delete mode 100644 implementation/13-refactoring/src/.gitkeep delete mode 100644 implementation/13-refactoring/src/Action/Hello.php delete mode 100644 implementation/13-refactoring/src/Action/Other.php delete mode 100644 implementation/13-refactoring/src/Bootstrap.php delete mode 100644 implementation/13-refactoring/src/Exception/InternalServerError.php delete mode 100644 implementation/13-refactoring/src/Exception/MethodNotAllowed.php delete mode 100644 implementation/13-refactoring/src/Exception/NotFound.php delete mode 100644 implementation/13-refactoring/src/Factory/ContainerProvider.php delete mode 100644 implementation/13-refactoring/src/Factory/DiactorosRequestFactory.php delete mode 100644 implementation/13-refactoring/src/Factory/FileSystemSettingsProvider.php delete mode 100644 implementation/13-refactoring/src/Factory/RequestFactory.php delete mode 100644 implementation/13-refactoring/src/Factory/SettingsContainerProvider.php delete mode 100644 implementation/13-refactoring/src/Factory/SettingsProvider.php delete mode 100644 implementation/13-refactoring/src/Http/BasicEmitter.php delete mode 100644 implementation/13-refactoring/src/Http/Emitter.php delete mode 100644 implementation/13-refactoring/src/Http/InvokerRoutedHandler.php delete mode 100644 implementation/13-refactoring/src/Http/RouteMiddleware.php delete mode 100644 implementation/13-refactoring/src/Http/RoutedRequestHandler.php delete mode 100644 implementation/13-refactoring/src/Kernel.php delete mode 100644 implementation/13-refactoring/src/Service/Time/Now.php delete mode 100644 implementation/13-refactoring/src/Service/Time/SystemClockNow.php delete mode 100644 implementation/13-refactoring/src/Settings.php delete mode 100644 implementation/13-refactoring/src/Template/MustacheRenderer.php delete mode 100644 implementation/13-refactoring/src/Template/Renderer.php delete mode 100644 implementation/13-refactoring/templates/hello.html delete mode 100644 implementation/14-middleware/.php-cs-fixer.php delete mode 100644 implementation/14-middleware/.phpcs.xml.dist delete mode 100644 implementation/14-middleware/composer.json delete mode 100644 implementation/14-middleware/composer.lock delete mode 100644 implementation/14-middleware/config/dependencies.php delete mode 100644 implementation/14-middleware/config/middlewares.php delete mode 100644 implementation/14-middleware/config/routes.php delete mode 100644 implementation/14-middleware/config/settings.php delete mode 100644 implementation/14-middleware/phpstan-baseline.neon delete mode 100644 implementation/14-middleware/phpstan.neon delete mode 100644 implementation/14-middleware/public/favicon.ico delete mode 100644 implementation/14-middleware/public/index.php delete mode 100644 implementation/14-middleware/src/.gitkeep delete mode 100644 implementation/14-middleware/src/Action/Hello.php delete mode 100644 implementation/14-middleware/src/Action/Other.php delete mode 100644 implementation/14-middleware/src/Bootstrap.php delete mode 100644 implementation/14-middleware/src/Exception/InternalServerError.php delete mode 100644 implementation/14-middleware/src/Exception/MethodNotAllowed.php delete mode 100644 implementation/14-middleware/src/Exception/NotFound.php delete mode 100644 implementation/14-middleware/src/Factory/ContainerProvider.php delete mode 100644 implementation/14-middleware/src/Factory/DiactorosRequestFactory.php delete mode 100644 implementation/14-middleware/src/Factory/FileSystemSettingsProvider.php delete mode 100644 implementation/14-middleware/src/Factory/PipelineProvider.php delete mode 100644 implementation/14-middleware/src/Factory/RequestFactory.php delete mode 100644 implementation/14-middleware/src/Factory/SettingsContainerProvider.php delete mode 100644 implementation/14-middleware/src/Factory/SettingsProvider.php delete mode 100644 implementation/14-middleware/src/Http/BasicEmitter.php delete mode 100644 implementation/14-middleware/src/Http/ContainerPipeline.php delete mode 100644 implementation/14-middleware/src/Http/Emitter.php delete mode 100644 implementation/14-middleware/src/Http/InvokerRoutedHandler.php delete mode 100644 implementation/14-middleware/src/Http/Pipeline.php delete mode 100644 implementation/14-middleware/src/Http/RouteMiddleware.php delete mode 100644 implementation/14-middleware/src/Http/RoutedRequestHandler.php delete mode 100644 implementation/14-middleware/src/Kernel.php delete mode 100644 implementation/14-middleware/src/Service/Time/Now.php delete mode 100644 implementation/14-middleware/src/Service/Time/SystemClockNow.php delete mode 100644 implementation/14-middleware/src/Settings.php delete mode 100644 implementation/14-middleware/src/Template/MustacheRenderer.php delete mode 100644 implementation/14-middleware/src/Template/Renderer.php delete mode 100644 implementation/14-middleware/templates/hello.html delete mode 100644 implementation/15-adding-content/.php-cs-fixer.php delete mode 100644 implementation/15-adding-content/.phpcs.xml.dist delete mode 100644 implementation/15-adding-content/cli-config.php delete mode 100644 implementation/15-adding-content/composer.json delete mode 100644 implementation/15-adding-content/composer.lock delete mode 100644 implementation/15-adding-content/config/dependencies.php delete mode 100644 implementation/15-adding-content/config/middlewares.php delete mode 100644 implementation/15-adding-content/config/routes.php delete mode 100644 implementation/15-adding-content/config/settings.php delete mode 100644 implementation/15-adding-content/data/pages/01-front-controller.md delete mode 100644 implementation/15-adding-content/data/pages/02-composer.md delete mode 100644 implementation/15-adding-content/data/pages/03-error-handler.md delete mode 100644 implementation/15-adding-content/data/pages/04-development-helpers.md delete mode 100644 implementation/15-adding-content/data/pages/05-http.md delete mode 100644 implementation/15-adding-content/data/pages/06-router.md delete mode 100644 implementation/15-adding-content/data/pages/07-dispatching-to-a-class.md delete mode 100644 implementation/15-adding-content/data/pages/08-inversion-of-control.md delete mode 100644 implementation/15-adding-content/data/pages/09-dependency-injector.md delete mode 100644 implementation/15-adding-content/data/pages/10-invoker.md delete mode 100644 implementation/15-adding-content/data/pages/11-templating.md delete mode 100644 implementation/15-adding-content/data/pages/12-configuration.md delete mode 100644 implementation/15-adding-content/data/pages/13-refactoring.md delete mode 100644 implementation/15-adding-content/data/pages/14-middleware.md delete mode 100644 implementation/15-adding-content/phpstan-baseline.neon delete mode 100644 implementation/15-adding-content/phpstan.neon delete mode 100644 implementation/15-adding-content/public/css/spectre-exp.min.css delete mode 100644 implementation/15-adding-content/public/css/spectre-icons.min.css delete mode 100644 implementation/15-adding-content/public/css/spectre.min.css delete mode 100644 implementation/15-adding-content/public/favicon.ico delete mode 100644 implementation/15-adding-content/public/index.php delete mode 100644 implementation/15-adding-content/src/.gitkeep delete mode 100644 implementation/15-adding-content/src/Action/Hello.php delete mode 100644 implementation/15-adding-content/src/Action/Other.php delete mode 100644 implementation/15-adding-content/src/Action/Page.php delete mode 100644 implementation/15-adding-content/src/Bootstrap.php delete mode 100644 implementation/15-adding-content/src/Exception/InternalServerError.php delete mode 100644 implementation/15-adding-content/src/Exception/MethodNotAllowed.php delete mode 100644 implementation/15-adding-content/src/Exception/NotFound.php delete mode 100644 implementation/15-adding-content/src/Factory/ContainerProvider.php delete mode 100644 implementation/15-adding-content/src/Factory/DiactorosRequestFactory.php delete mode 100644 implementation/15-adding-content/src/Factory/DoctrineEm.php delete mode 100644 implementation/15-adding-content/src/Factory/FileSystemSettingsProvider.php delete mode 100644 implementation/15-adding-content/src/Factory/PipelineProvider.php delete mode 100644 implementation/15-adding-content/src/Factory/RequestFactory.php delete mode 100644 implementation/15-adding-content/src/Factory/SettingsContainerProvider.php delete mode 100644 implementation/15-adding-content/src/Factory/SettingsProvider.php delete mode 100644 implementation/15-adding-content/src/Http/BasicEmitter.php delete mode 100644 implementation/15-adding-content/src/Http/ContainerPipeline.php delete mode 100644 implementation/15-adding-content/src/Http/Emitter.php delete mode 100644 implementation/15-adding-content/src/Http/InvokerRoutedHandler.php delete mode 100644 implementation/15-adding-content/src/Http/Pipeline.php delete mode 100644 implementation/15-adding-content/src/Http/RouteMiddleware.php delete mode 100644 implementation/15-adding-content/src/Http/RoutedRequestHandler.php delete mode 100644 implementation/15-adding-content/src/Kernel.php delete mode 100644 implementation/15-adding-content/src/Middleware/CacheMiddleware.php delete mode 100644 implementation/15-adding-content/src/Model/MarkdownPage.php delete mode 100644 implementation/15-adding-content/src/Repository/CachedMarkdownPageRepo.php delete mode 100644 implementation/15-adding-content/src/Repository/DoctrineMarkdownPageRepo.php delete mode 100644 implementation/15-adding-content/src/Repository/MarkdownPageFilesystem.php delete mode 100644 implementation/15-adding-content/src/Repository/MarkdownPageRepo.php delete mode 100644 implementation/15-adding-content/src/Service/Time/Now.php delete mode 100644 implementation/15-adding-content/src/Service/Time/SystemClockNow.php delete mode 100644 implementation/15-adding-content/src/Settings.php delete mode 100644 implementation/15-adding-content/src/Template/GithubMarkdownRenderer.php delete mode 100644 implementation/15-adding-content/src/Template/MarkdownParser.php delete mode 100644 implementation/15-adding-content/src/Template/MustacheRenderer.php delete mode 100644 implementation/15-adding-content/src/Template/ParsedownParser.php delete mode 100644 implementation/15-adding-content/src/Template/Renderer.php delete mode 100644 implementation/15-adding-content/templates/hello.html delete mode 100644 implementation/15-adding-content/templates/page.html delete mode 100644 implementation/15-adding-content/templates/page/list.html delete mode 100644 implementation/15-adding-content/templates/page/show.html delete mode 100644 implementation/15-adding-content/templates/pagelist.html delete mode 100644 implementation/15-adding-content/templates/partials/foot.html delete mode 100644 implementation/15-adding-content/templates/partials/head.html delete mode 100644 implementation/16-caching/.php-cs-fixer.php delete mode 100644 implementation/16-caching/.phpcs.xml.dist delete mode 100644 implementation/16-caching/composer.json delete mode 100644 implementation/16-caching/composer.lock delete mode 100644 implementation/16-caching/config/dependencies.php delete mode 100644 implementation/16-caching/config/middlewares.php delete mode 100644 implementation/16-caching/config/routes.php delete mode 100644 implementation/16-caching/config/settings.php delete mode 100644 implementation/16-caching/data/pages/01-front-controller.md delete mode 100644 implementation/16-caching/data/pages/02-composer.md delete mode 100644 implementation/16-caching/data/pages/03-error-handler.md delete mode 100644 implementation/16-caching/data/pages/04-development-helpers.md delete mode 100644 implementation/16-caching/data/pages/05-http.md delete mode 100644 implementation/16-caching/data/pages/06-router.md delete mode 100644 implementation/16-caching/data/pages/07-dispatching-to-a-class.md delete mode 100644 implementation/16-caching/data/pages/08-inversion-of-control.md delete mode 100644 implementation/16-caching/data/pages/09-dependency-injector.md delete mode 100644 implementation/16-caching/data/pages/10-invoker.md delete mode 100644 implementation/16-caching/data/pages/11-templating.md delete mode 100644 implementation/16-caching/data/pages/12-configuration.md delete mode 100644 implementation/16-caching/data/pages/13-refactoring.md delete mode 100644 implementation/16-caching/data/pages/14-middleware.md delete mode 100644 implementation/16-caching/phpstan-baseline.neon delete mode 100644 implementation/16-caching/phpstan.neon delete mode 100644 implementation/16-caching/public/css/spectre-exp.min.css delete mode 100644 implementation/16-caching/public/css/spectre-icons.min.css delete mode 100644 implementation/16-caching/public/css/spectre.min.css delete mode 100644 implementation/16-caching/public/favicon.ico delete mode 100644 implementation/16-caching/public/index.php delete mode 100644 implementation/16-caching/src/.gitkeep delete mode 100644 implementation/16-caching/src/Action/Hello.php delete mode 100644 implementation/16-caching/src/Action/Other.php delete mode 100644 implementation/16-caching/src/Action/Page.php delete mode 100644 implementation/16-caching/src/Bootstrap.php delete mode 100644 implementation/16-caching/src/Exception/InternalServerError.php delete mode 100644 implementation/16-caching/src/Exception/MethodNotAllowed.php delete mode 100644 implementation/16-caching/src/Exception/NotFound.php delete mode 100644 implementation/16-caching/src/Factory/ContainerProvider.php delete mode 100644 implementation/16-caching/src/Factory/DiactorosRequestFactory.php delete mode 100644 implementation/16-caching/src/Factory/FileSystemSettingsProvider.php delete mode 100644 implementation/16-caching/src/Factory/PipelineProvider.php delete mode 100644 implementation/16-caching/src/Factory/RequestFactory.php delete mode 100644 implementation/16-caching/src/Factory/SettingsContainerProvider.php delete mode 100644 implementation/16-caching/src/Factory/SettingsProvider.php delete mode 100644 implementation/16-caching/src/Http/BasicEmitter.php delete mode 100644 implementation/16-caching/src/Http/ContainerPipeline.php delete mode 100644 implementation/16-caching/src/Http/Emitter.php delete mode 100644 implementation/16-caching/src/Http/InvokerRoutedHandler.php delete mode 100644 implementation/16-caching/src/Http/Pipeline.php delete mode 100644 implementation/16-caching/src/Http/RouteMiddleware.php delete mode 100644 implementation/16-caching/src/Http/RoutedRequestHandler.php delete mode 100644 implementation/16-caching/src/Kernel.php delete mode 100644 implementation/16-caching/src/Middleware/CacheMiddleware.php delete mode 100644 implementation/16-caching/src/Model/MarkdownPage.php delete mode 100644 implementation/16-caching/src/Repository/CachedMarkdownPageRepo.php delete mode 100644 implementation/16-caching/src/Repository/MarkdownPageFilesystem.php delete mode 100644 implementation/16-caching/src/Repository/MarkdownPageRepo.php delete mode 100644 implementation/16-caching/src/Service/Time/Now.php delete mode 100644 implementation/16-caching/src/Service/Time/SystemClockNow.php delete mode 100644 implementation/16-caching/src/Settings.php delete mode 100644 implementation/16-caching/src/Template/MustacheRenderer.php delete mode 100644 implementation/16-caching/src/Template/Renderer.php delete mode 100644 implementation/16-caching/templates/hello.html delete mode 100644 implementation/16-caching/templates/page.html delete mode 100644 implementation/16-caching/templates/partials/foot.html delete mode 100644 implementation/16-caching/templates/partials/head.html delete mode 100644 implementation/16-data-repository/.php-cs-fixer.php delete mode 100644 implementation/16-data-repository/.phpcs.xml.dist delete mode 100644 implementation/16-data-repository/cli-config.php delete mode 100644 implementation/16-data-repository/composer.json delete mode 100644 implementation/16-data-repository/composer.lock delete mode 100644 implementation/16-data-repository/config/dependencies.php delete mode 100644 implementation/16-data-repository/config/middlewares.php delete mode 100644 implementation/16-data-repository/config/routes.php delete mode 100644 implementation/16-data-repository/config/settings.php delete mode 100644 implementation/16-data-repository/data/pages/01-front-controller.md delete mode 100644 implementation/16-data-repository/data/pages/02-composer.md delete mode 100644 implementation/16-data-repository/data/pages/03-error-handler.md delete mode 100644 implementation/16-data-repository/data/pages/04-development-helpers.md delete mode 100644 implementation/16-data-repository/data/pages/05-http.md delete mode 100644 implementation/16-data-repository/data/pages/06-router.md delete mode 100644 implementation/16-data-repository/data/pages/07-dispatching-to-a-class.md delete mode 100644 implementation/16-data-repository/data/pages/08-inversion-of-control.md delete mode 100644 implementation/16-data-repository/data/pages/09-dependency-injector.md delete mode 100644 implementation/16-data-repository/data/pages/10-invoker.md delete mode 100644 implementation/16-data-repository/data/pages/11-templating.md delete mode 100644 implementation/16-data-repository/data/pages/12-configuration.md delete mode 100644 implementation/16-data-repository/data/pages/13-refactoring.md delete mode 100644 implementation/16-data-repository/data/pages/14-middleware.md delete mode 100644 implementation/16-data-repository/phpstan-baseline.neon delete mode 100644 implementation/16-data-repository/phpstan.neon delete mode 100644 implementation/16-data-repository/public/css/spectre-exp.min.css delete mode 100644 implementation/16-data-repository/public/css/spectre-icons.min.css delete mode 100644 implementation/16-data-repository/public/css/spectre.min.css delete mode 100644 implementation/16-data-repository/public/favicon.ico delete mode 100644 implementation/16-data-repository/public/index.php delete mode 100644 implementation/16-data-repository/src/.gitkeep delete mode 100644 implementation/16-data-repository/src/Action/Hello.php delete mode 100644 implementation/16-data-repository/src/Action/Other.php delete mode 100644 implementation/16-data-repository/src/Action/Page.php delete mode 100644 implementation/16-data-repository/src/Bootstrap.php delete mode 100644 implementation/16-data-repository/src/Exception/InternalServerError.php delete mode 100644 implementation/16-data-repository/src/Exception/MethodNotAllowed.php delete mode 100644 implementation/16-data-repository/src/Exception/NotFound.php delete mode 100644 implementation/16-data-repository/src/Factory/ContainerProvider.php delete mode 100644 implementation/16-data-repository/src/Factory/DiactorosRequestFactory.php delete mode 100644 implementation/16-data-repository/src/Factory/DoctrineEm.php delete mode 100644 implementation/16-data-repository/src/Factory/FileSystemSettingsProvider.php delete mode 100644 implementation/16-data-repository/src/Factory/PipelineProvider.php delete mode 100644 implementation/16-data-repository/src/Factory/RequestFactory.php delete mode 100644 implementation/16-data-repository/src/Factory/SettingsContainerProvider.php delete mode 100644 implementation/16-data-repository/src/Factory/SettingsProvider.php delete mode 100644 implementation/16-data-repository/src/Http/BasicEmitter.php delete mode 100644 implementation/16-data-repository/src/Http/ContainerPipeline.php delete mode 100644 implementation/16-data-repository/src/Http/Emitter.php delete mode 100644 implementation/16-data-repository/src/Http/InvokerRoutedHandler.php delete mode 100644 implementation/16-data-repository/src/Http/Pipeline.php delete mode 100644 implementation/16-data-repository/src/Http/RouteMiddleware.php delete mode 100644 implementation/16-data-repository/src/Http/RoutedRequestHandler.php delete mode 100644 implementation/16-data-repository/src/Kernel.php delete mode 100644 implementation/16-data-repository/src/Model/MarkdownPage.php delete mode 100644 implementation/16-data-repository/src/Repository/FileSystemMarkdownPageRepo.php delete mode 100644 implementation/16-data-repository/src/Repository/MarkdownPageRepo.php delete mode 100644 implementation/16-data-repository/src/Service/Time/Now.php delete mode 100644 implementation/16-data-repository/src/Service/Time/SystemClockNow.php delete mode 100644 implementation/16-data-repository/src/Settings.php delete mode 100644 implementation/16-data-repository/src/Template/MarkdownParser.php delete mode 100644 implementation/16-data-repository/src/Template/MustacheRenderer.php delete mode 100644 implementation/16-data-repository/src/Template/ParsedownParser.php delete mode 100644 implementation/16-data-repository/src/Template/Renderer.php delete mode 100644 implementation/16-data-repository/templates/hello.html delete mode 100644 implementation/16-data-repository/templates/page.html delete mode 100644 implementation/16-data-repository/templates/page/list.html delete mode 100644 implementation/16-data-repository/templates/page/show.html delete mode 100644 implementation/16-data-repository/templates/pagelist.html delete mode 100644 implementation/16-data-repository/templates/partials/foot.html delete mode 100644 implementation/16-data-repository/templates/partials/head.html delete mode 100644 implementation/18-caching/.php-cs-fixer.php delete mode 100644 implementation/18-caching/.phpcs.xml.dist delete mode 100644 implementation/18-caching/cli-config.php delete mode 100644 implementation/18-caching/composer.json delete mode 100644 implementation/18-caching/composer.lock delete mode 100644 implementation/18-caching/config/dependencies.php delete mode 100644 implementation/18-caching/config/middlewares.php delete mode 100644 implementation/18-caching/config/routes.php delete mode 100644 implementation/18-caching/config/settings.php delete mode 100644 implementation/18-caching/data/pages/01-front-controller.md delete mode 100644 implementation/18-caching/data/pages/02-composer.md delete mode 100644 implementation/18-caching/data/pages/03-error-handler.md delete mode 100644 implementation/18-caching/data/pages/04-development-helpers.md delete mode 100644 implementation/18-caching/data/pages/05-http.md delete mode 100644 implementation/18-caching/data/pages/06-router.md delete mode 100644 implementation/18-caching/data/pages/07-dispatching-to-a-class.md delete mode 100644 implementation/18-caching/data/pages/08-inversion-of-control.md delete mode 100644 implementation/18-caching/data/pages/09-dependency-injector.md delete mode 100644 implementation/18-caching/data/pages/10-invoker.md delete mode 100644 implementation/18-caching/data/pages/11-templating.md delete mode 100644 implementation/18-caching/data/pages/12-configuration.md delete mode 100644 implementation/18-caching/data/pages/13-refactoring.md delete mode 100644 implementation/18-caching/data/pages/14-middleware.md delete mode 100644 implementation/18-caching/data/pages/15-adding-content.md delete mode 100644 implementation/18-caching/data/pages/16-data-repository.md delete mode 100644 implementation/18-caching/data/pages/17-performance.md delete mode 100644 implementation/18-caching/data/pages/18-caching.md delete mode 100644 implementation/18-caching/phpstan-baseline.neon delete mode 100644 implementation/18-caching/phpstan.neon delete mode 100644 implementation/18-caching/public/css/spectre-exp.min.css delete mode 100644 implementation/18-caching/public/css/spectre-icons.min.css delete mode 100644 implementation/18-caching/public/css/spectre.min.css delete mode 100644 implementation/18-caching/public/favicon.ico delete mode 100644 implementation/18-caching/public/index.php delete mode 100644 implementation/18-caching/src/.gitkeep delete mode 100644 implementation/18-caching/src/Action/Hello.php delete mode 100644 implementation/18-caching/src/Action/Other.php delete mode 100644 implementation/18-caching/src/Action/Page.php delete mode 100644 implementation/18-caching/src/Bootstrap.php delete mode 100644 implementation/18-caching/src/Exception/InternalServerError.php delete mode 100644 implementation/18-caching/src/Exception/MethodNotAllowed.php delete mode 100644 implementation/18-caching/src/Exception/NotFound.php delete mode 100644 implementation/18-caching/src/Factory/ContainerProvider.php delete mode 100644 implementation/18-caching/src/Factory/DiactorosRequestFactory.php delete mode 100644 implementation/18-caching/src/Factory/FileSystemSettingsProvider.php delete mode 100644 implementation/18-caching/src/Factory/PipelineProvider.php delete mode 100644 implementation/18-caching/src/Factory/RequestFactory.php delete mode 100644 implementation/18-caching/src/Factory/SettingsContainerProvider.php delete mode 100644 implementation/18-caching/src/Factory/SettingsProvider.php delete mode 100644 implementation/18-caching/src/Http/BasicEmitter.php delete mode 100644 implementation/18-caching/src/Http/ContainerPipeline.php delete mode 100644 implementation/18-caching/src/Http/Emitter.php delete mode 100644 implementation/18-caching/src/Http/InvokerRoutedHandler.php delete mode 100644 implementation/18-caching/src/Http/Pipeline.php delete mode 100644 implementation/18-caching/src/Http/RouteMiddleware.php delete mode 100644 implementation/18-caching/src/Http/RoutedRequestHandler.php delete mode 100644 implementation/18-caching/src/Kernel.php delete mode 100644 implementation/18-caching/src/Middleware/Cache.php delete mode 100644 implementation/18-caching/src/Model/MarkdownPage.php delete mode 100644 implementation/18-caching/src/Repository/CachedMarkdownPageRepo.php delete mode 100644 implementation/18-caching/src/Repository/FileSystemMarkdownPageRepo.php delete mode 100644 implementation/18-caching/src/Repository/MarkdownPageRepo.php delete mode 100644 implementation/18-caching/src/Service/Cache/ApcuCache.php delete mode 100644 implementation/18-caching/src/Service/Cache/EasyCache.php delete mode 100644 implementation/18-caching/src/Service/Time/Now.php delete mode 100644 implementation/18-caching/src/Service/Time/SystemClockNow.php delete mode 100644 implementation/18-caching/src/Settings.php delete mode 100644 implementation/18-caching/src/Template/MarkdownParser.php delete mode 100644 implementation/18-caching/src/Template/MustacheRenderer.php delete mode 100644 implementation/18-caching/src/Template/ParsedownParser.php delete mode 100644 implementation/18-caching/src/Template/Renderer.php delete mode 100644 implementation/18-caching/templates/hello.html delete mode 100644 implementation/18-caching/templates/page.html delete mode 100644 implementation/18-caching/templates/page/list.html delete mode 100644 implementation/18-caching/templates/page/show.html delete mode 100644 implementation/18-caching/templates/pagelist.html delete mode 100644 implementation/18-caching/templates/partials/foot.html delete mode 100644 implementation/18-caching/templates/partials/head.html delete mode 100644 to-be-continued.md diff --git a/app/.php-cs-fixer.php b/app/.php-cs-fixer.php deleted file mode 100644 index 705a7d7..0000000 --- a/app/.php-cs-fixer.php +++ /dev/null @@ -1,38 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config' - ]) - ); \ No newline at end of file diff --git a/app/.phpcs.xml.dist b/app/.phpcs.xml.dist deleted file mode 100644 index 3b433f6..0000000 --- a/app/.phpcs.xml.dist +++ /dev/null @@ -1,9 +0,0 @@ - - - - - src - config - - - \ No newline at end of file diff --git a/app/cli-config.php b/app/cli-config.php deleted file mode 100644 index fbc6598..0000000 --- a/app/cli-config.php +++ /dev/null @@ -1,13 +0,0 @@ -getContainer(); - -return ConsoleRunner::createHelperSet($container->get(EntityManagerInterface::class)); diff --git a/app/composer.json b/app/composer.json deleted file mode 100644 index 29695da..0000000 --- a/app/composer.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3", - "mustache/mustache": "^2.14", - "psr/http-server-middleware": "^1.0", - "middlewares/trailing-slash": "^2.0", - "middlewares/whoops": "^2.0", - "erusev/parsedown": "^1.7", - "league/commonmark": "^2.2", - "ext-apcu": "*", - "ext-zend-opcache": "*" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require-dev": { - "phpstan/phpstan": "^1.5", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0", - "squizlabs/php_codesniffer": "^3.6", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1", - "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0" - }, - "config": { - "optimize-autoloader": true, - "allow-plugins": { - "phpstan/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "serve": [ - "Composer\\Config::disableProcessTimeout", - "php -S 0.0.0.0:1234 -t public" - ], - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" - } -} diff --git a/app/composer.lock b/app/composer.lock deleted file mode 100644 index 40cd7d3..0000000 --- a/app/composer.lock +++ /dev/null @@ -1,2440 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "9a29468fd456190a9fbcff98ed42d862", - "packages": [ - { - "name": "dflydev/dot-access-data", - "version": "v3.0.1", - "source": { - "type": "git", - "url": "https://github.com/dflydev/dflydev-dot-access-data.git", - "reference": "0992cc19268b259a39e86f296da5f0677841f42c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", - "reference": "0992cc19268b259a39e86f296da5f0677841f42c", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.42", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", - "scrutinizer/ocular": "1.6.0", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^3.14" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Dflydev\\DotAccessData\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dragonfly Development Inc.", - "email": "info@dflydev.com", - "homepage": "http://dflydev.com" - }, - { - "name": "Beau Simensen", - "email": "beau@dflydev.com", - "homepage": "http://beausimensen.com" - }, - { - "name": "Carlos Frutos", - "email": "carlos@kiwing.it", - "homepage": "https://github.com/cfrutos" - }, - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com" - } - ], - "description": "Given a deep data structure, access data by dot notation.", - "homepage": "https://github.com/dflydev/dflydev-dot-access-data", - "keywords": [ - "access", - "data", - "dot", - "notation" - ], - "support": { - "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", - "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" - }, - "time": "2021-08-13T13:06:58+00:00" - }, - { - "name": "erusev/parsedown", - "version": "1.7.4", - "source": { - "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35" - }, - "type": "library", - "autoload": { - "psr-0": { - "Parsedown": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com" - } - ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org", - "keywords": [ - "markdown", - "parser" - ], - "support": { - "issues": "https://github.com/erusev/parsedown/issues", - "source": "https://github.com/erusev/parsedown/tree/1.7.x" - }, - "time": "2019-12-30T22:54:17+00:00" - }, - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.9.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2022-03-29T20:12:16+00:00" - }, - { - "name": "league/commonmark", - "version": "2.2.3", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/commonmark.git", - "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", - "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "league/config": "^1.1.1", - "php": "^7.4 || ^8.0", - "psr/event-dispatcher": "^1.0", - "symfony/deprecation-contracts": "^2.1 || ^3.0", - "symfony/polyfill-php80": "^1.15" - }, - "require-dev": { - "cebe/markdown": "^1.0", - "commonmark/cmark": "0.30.0", - "commonmark/commonmark.js": "0.30.0", - "composer/package-versions-deprecated": "^1.8", - "erusev/parsedown": "^1.0", - "ext-json": "*", - "github/gfm": "0.29.0", - "michelf/php-markdown": "^1.4", - "phpstan/phpstan": "^0.12.88 || ^1.0.0", - "phpunit/phpunit": "^9.5.5", - "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", - "unleashedtech/php-coding-standard": "^3.1", - "vimeo/psalm": "^4.7.3" - }, - "suggest": { - "symfony/yaml": "v2.3+ required if using the Front Matter extension" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.3-dev" - } - }, - "autoload": { - "psr-4": { - "League\\CommonMark\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com", - "role": "Lead Developer" - } - ], - "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", - "homepage": "https://commonmark.thephpleague.com", - "keywords": [ - "commonmark", - "flavored", - "gfm", - "github", - "github-flavored", - "markdown", - "md", - "parser" - ], - "support": { - "docs": "https://commonmark.thephpleague.com/", - "forum": "https://github.com/thephpleague/commonmark/discussions", - "issues": "https://github.com/thephpleague/commonmark/issues", - "rss": "https://github.com/thephpleague/commonmark/releases.atom", - "source": "https://github.com/thephpleague/commonmark" - }, - "funding": [ - { - "url": "https://www.colinodell.com/sponsor", - "type": "custom" - }, - { - "url": "https://www.paypal.me/colinpodell/10.00", - "type": "custom" - }, - { - "url": "https://github.com/colinodell", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/commonmark", - "type": "tidelift" - } - ], - "time": "2022-02-26T21:24:45+00:00" - }, - { - "name": "league/config", - "version": "v1.1.1", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/config.git", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "shasum": "" - }, - "require": { - "dflydev/dot-access-data": "^3.0.1", - "nette/schema": "^1.2", - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.90", - "phpunit/phpunit": "^9.5.5", - "scrutinizer/ocular": "^1.8.1", - "unleashedtech/php-coding-standard": "^3.1", - "vimeo/psalm": "^4.7.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.2-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Config\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com", - "role": "Lead Developer" - } - ], - "description": "Define configuration arrays with strict schemas and access values with dot notation", - "homepage": "https://config.thephpleague.com", - "keywords": [ - "array", - "config", - "configuration", - "dot", - "dot-access", - "nested", - "schema" - ], - "support": { - "docs": "https://config.thephpleague.com/", - "issues": "https://github.com/thephpleague/config/issues", - "rss": "https://github.com/thephpleague/config/releases.atom", - "source": "https://github.com/thephpleague/config" - }, - "funding": [ - { - "url": "https://www.colinodell.com/sponsor", - "type": "custom" - }, - { - "url": "https://www.paypal.me/colinpodell/10.00", - "type": "custom" - }, - { - "url": "https://github.com/colinodell", - "type": "github" - } - ], - "time": "2021-08-14T12:15:32+00:00" - }, - { - "name": "middlewares/trailing-slash", - "version": "v2.0.1", - "source": { - "type": "git", - "url": "https://github.com/middlewares/trailing-slash.git", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", - "shasum": "" - }, - "require": { - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to normalize the trailing slash of the uri path", - "homepage": "https://github.com/middlewares/trailing-slash", - "keywords": [ - "http", - "middleware", - "normalize", - "path", - "psr-15", - "psr-7", - "slash" - ], - "support": { - "issues": "https://github.com/middlewares/trailing-slash/issues", - "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" - }, - "time": "2020-12-02T00:06:55+00:00" - }, - { - "name": "middlewares/utils", - "version": "v3.3.0", - "source": { - "type": "git", - "url": "https://github.com/middlewares/utils.git", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^v2.16", - "guzzlehttp/psr7": "^2.0", - "laminas/laminas-diactoros": "^2.4", - "nyholm/psr7": "^1.0", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "slim/psr7": "^1.4", - "squizlabs/php_codesniffer": "^3.5", - "sunrise/http-message": "^1.0", - "sunrise/http-server-request": "^1.0", - "sunrise/stream": "^1.0.15", - "sunrise/uri": "^1.0.15" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\Utils\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Common utils for PSR-15 middleware packages", - "homepage": "https://github.com/middlewares/utils", - "keywords": [ - "PSR-11", - "http", - "middleware", - "psr-15", - "psr-17", - "psr-7" - ], - "support": { - "issues": "https://github.com/middlewares/utils/issues", - "source": "https://github.com/middlewares/utils/tree/v3.3.0" - }, - "time": "2021-07-04T17:56:23+00:00" - }, - { - "name": "middlewares/whoops", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/middlewares/whoops.git", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", - "shasum": "" - }, - "require": { - "filp/whoops": "^2.5", - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "eloquent/phony-phpunit": "^5.0 || ^7.0", - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to use Whoops as error handler", - "homepage": "https://github.com/middlewares/whoops", - "keywords": [ - "error", - "http", - "middleware", - "psr-15", - "psr-7", - "server", - "whoops" - ], - "support": { - "issues": "https://github.com/middlewares/whoops/issues", - "source": "https://github.com/middlewares/whoops/tree/v2.0.2" - }, - "time": "2022-01-27T20:31:30+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nette/schema", - "version": "v1.2.2", - "source": { - "type": "git", - "url": "https://github.com/nette/schema.git", - "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", - "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", - "shasum": "" - }, - "require": { - "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": ">=7.1 <8.2" - }, - "require-dev": { - "nette/tester": "^2.3 || ^2.4", - "phpstan/phpstan-nette": "^0.12", - "tracy/tracy": "^2.7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "📐 Nette Schema: validating data structures against a given Schema.", - "homepage": "https://nette.org", - "keywords": [ - "config", - "nette" - ], - "support": { - "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.2" - }, - "time": "2021-10-15T11:40:02+00:00" - }, - { - "name": "nette/utils", - "version": "v3.2.7", - "source": { - "type": "git", - "url": "https://github.com/nette/utils.git", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", - "shasum": "" - }, - "require": { - "php": ">=7.2 <8.2" - }, - "conflict": { - "nette/di": "<3.0.6" - }, - "require-dev": { - "nette/tester": "~2.0", - "phpstan/phpstan": "^1.0", - "tracy/tracy": "^2.3" - }, - "suggest": { - "ext-gd": "to use Image", - "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", - "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", - "ext-json": "to use Nette\\Utils\\Json", - "ext-mbstring": "to use Strings::lower() etc...", - "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", - "ext-xml": "to use Strings::length() etc. when mbstring is not available" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", - "homepage": "https://nette.org", - "keywords": [ - "array", - "core", - "datetime", - "images", - "json", - "nette", - "paginator", - "password", - "slugify", - "string", - "unicode", - "utf-8", - "utility", - "validation" - ], - "support": { - "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.2.7" - }, - "time": "2022-01-24T11:29:14+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/http-server-middleware", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-middleware.git", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0", - "psr/http-server-handler": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side middleware", - "keywords": [ - "http", - "http-interop", - "middleware", - "psr", - "psr-15", - "psr-7", - "request", - "response" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-middleware/issues", - "source": "https://github.com/php-fig/http-server-middleware/tree/master" - }, - "time": "2018-10-30T17:12:04+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.0.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "shasum": "" - }, - "require": { - "php": ">=8.0.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:55:41+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-04T08:16:47+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "doctrine/coding-standard", - "version": "8.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/coding-standard.git", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "slevomat/coding-standard": "^6.4.1", - "squizlabs/php_codesniffer": "^3.5.8" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Steve Müller", - "email": "st.mueller@dzh-online.de" - } - ], - "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", - "keywords": [ - "checks", - "code", - "coding", - "cs", - "doctrine", - "rules", - "sniffer", - "sniffs", - "standard", - "style" - ], - "support": { - "issues": "https://github.com/doctrine/coding-standard/issues", - "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" - }, - "time": "2021-04-03T10:54:55+00:00" - }, - { - "name": "mnapoli/hard-mode", - "version": "0.3.0", - "source": { - "type": "git", - "url": "https://github.com/mnapoli/hard-mode.git", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", - "shasum": "" - }, - "require": { - "doctrine/coding-standard": "^8.0" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Hard mode for PHP", - "support": { - "issues": "https://github.com/mnapoli/hard-mode/issues", - "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" - }, - "time": "2020-10-12T07:54:37+00:00" - }, - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.9", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.26", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/master" - }, - "time": "2020-08-03T20:32:43+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.5.4", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", - "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.4" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-04-03T12:39:00+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^1.2.0" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" - }, - "time": "2021-11-18T09:30:29+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "6.4.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpstan/phpstan": "0.12.48", - "phpstan/phpstan-deprecation-rules": "0.12.5", - "phpstan/phpstan-phpunit": "0.12.16", - "phpstan/phpstan-strict-rules": "0.12.5", - "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2020-10-05T12:39:37+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1", - "ext-apcu": "*", - "ext-zend-opcache": "*" - }, - "platform-dev": [], - "plugin-api-version": "2.3.0" -} diff --git a/app/config/dependencies.php b/app/config/dependencies.php deleted file mode 100644 index df815c6..0000000 --- a/app/config/dependencies.php +++ /dev/null @@ -1,58 +0,0 @@ - fn (SystemClockNow $n) => $n, - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, - Emitter::class => fn (BasicEmitter $e) => $e, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, - MarkdownParser::class => fn (ParsedownParser $p) => $p, - MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, - EasyCache::class => fn (ApcuCache $c) => $c, - CachedMarkdownPageRepo::class => fn (EasyCache $c, FileSystemMarkdownPageRepo $r) => new CachedMarkdownPageRepo($c, $r), - - - // Factories - ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), - Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), -]; diff --git a/app/config/middlewares.php b/app/config/middlewares.php deleted file mode 100644 index ab662be..0000000 --- a/app/config/middlewares.php +++ /dev/null @@ -1,13 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/page', [Page::class, 'list']); - $r->addRoute('GET', '/page/{page}', [Page::class, 'show']); - $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); - $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -}; diff --git a/app/config/settings.php b/app/config/settings.php deleted file mode 100644 index c654565..0000000 --- a/app/config/settings.php +++ /dev/null @@ -1,12 +0,0 @@ ->](02-composer.md) - -### Front Controller - -A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. - -To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. - -A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. - -The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. - -But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. - -So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. - -Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: - -```php ->](02-composer.md) diff --git a/app/data/pages/02-composer.md b/app/data/pages/02-composer.md deleted file mode 100644 index a25a4a8..0000000 --- a/app/data/pages/02-composer.md +++ /dev/null @@ -1,75 +0,0 @@ -[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) - -### Composer - -[Composer](https://getcomposer.org/) is a dependency manager for PHP. - -Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do -something. With Composer, you can install third-party libraries for your application. - -If you don't have Composer installed already, head over to the website and install it. You can find Composer packages -for your project on [Packagist](https://packagist.org/). - -Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will -be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. - -Add the following content to the file: - -```json -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubiana", - "email": "lubiana@hannover.ccc.de" - } - ] -} -``` - -In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use -whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. -Just replace it with your namespace in your own code. - -I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. - -As the Bootstrap.php file is placed in that directory we should -add the namespace to the File as well. Here is my current Bootstrap.php -as a reference: - -```php ->](03-error-handler.md) diff --git a/app/data/pages/03-error-handler.md b/app/data/pages/03-error-handler.md deleted file mode 100644 index 60465d0..0000000 --- a/app/data/pages/03-error-handler.md +++ /dev/null @@ -1,79 +0,0 @@ -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) - -### Error Handler - -An error handler allows you to customize what happens if your code results in an error. - -A nice error page with a lot of information for debugging goes a long way during development. So the first package -for your application will take care of that. - -I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. -If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, -you have total control over your project. - -An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) - -To install a new package, open up your `composer.json` and add the package to the require part. It should now look -like this: - -```php -"require": { - "php": ">=8.1.0", - "filp/whoops": "^2.14" -}, -``` - -Now run `composer update` in your console and it will be installed. - -Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, -i that case composer automatically installs the package and updates your composer.json-file. - -But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, -ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you -only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. - -**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message -can help someone to gain access to your system. Always show a user friendly error page instead and send an email to -yourself, write to a log or something similar. So only you can see the errors in the production environment. - -For development that does not make sense though -- you want a nice error page. The solution is to have an environment -switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in -case no environment has been set. - -Then after the error handler registration, throw an `Exception` to test if everything is working correctly. -Your `Bootstrap.php` should now look similar to this: - -```php -pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (\Throwable $e) { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -throw new \Exception("Ooooopsie"); - -``` - -You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until -you get it working. Now would also be a good time for another commit. - - -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/app/data/pages/04-development-helpers.md b/app/data/pages/04-development-helpers.md deleted file mode 100644 index 74f913c..0000000 --- a/app/data/pages/04-development-helpers.md +++ /dev/null @@ -1,260 +0,0 @@ -[<< previous](03-error-handler.md) | [next >>](05-http.md) - -### Development Helpers - -I have added some more helpers to my composer.json that help me with development. As these are scripts and programms -used only for development they should not be used in a production environment. Composer has a specific sections in its -file called "dev-dependencies", everything that is required in this section does not get installen in production. - -Let's install our dev-helpers and i will explain them one by one: -`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` - -#### Static Code Analysis with phpstan - -Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or -create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements -that are not (or not always) available, if-statements that always are true and a lot of other stuff. - -A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. - -```php -/** - * @param \DateTime $date - * @return void - */ -function printDate($date) { - $date->format('Y-m-d H:i:s'); -} - -printDate('now'); -``` -if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` - -It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has -no use, and secondly, that we are calling the function with a string instead of a datetime object. - -```shell -1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - ------ --------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ --------------------------------------------------------------------------------------------- -30 Call to method DateTime::format() on a separate line has no effect. -33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. - ------ --------------------------------------------------------------------------------------------- -``` - -The second error is something that "declare strict-types" already catches for us, but the first error is something that -we usually would not discover easily without speccially looking for this errortype. - -We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and -path everytime we want to check our code for errors: - -```yaml -parameters: - level: max - paths: - - src -``` -now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project - -With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us -some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. - -```shell -composer require --dev phpstan/extension-installer -composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules -``` - -During the first install you need to allow the extension installer to actually install the extension. The second command -installs some more strict rulesets and activates them in phpstan. - -If we now rerun phpstan it already tells us about some errors we have made: - -``` - ------ ----------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ ----------------------------------------------------------------------------------------------- -10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider - using long ternary. -25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: - http://bit.ly/subtypeexception -26 Unreachable statement - code above always terminates. - ------ ----------------------------------------------------------------------------------------------- -``` - -The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove -that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific -problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working -on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages -everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we -are adding to our code. - -In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the -phpstan.neon file and run phpstan with the -'--generate-baseline' option: - -```yaml -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` -```shell -[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline -Note: Using configuration file /home/vagrant/app/phpstan.neon. - 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - - - [OK] Baseline generated with 1 error. - - -``` - -you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) - -#### PHP-CS-Fixer - -Another great tool is the php-cs-fixer, which just applies a specific style to your code. - -when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current -directory. - -You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) - -personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup -a configuration file that i use in all my projects .php-cs-fixer.php: - -```php -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - ]) - ); -``` - -#### PHP Codesniffer - -The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra -rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. - -it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. - -Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have -guessed we are adding some extra rules. - -Lets install the ruleset with composer -```shell -composer require --dev mnapoli/hard-mode -``` - -and add a configuration file to actually use it '.phpcs.xml.dist' -```xml - - - - - src - - - -``` - -running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. - -``` -[vagrant@archlinux app]$ ./vendor/bin/phpcs - -FILE: src/Bootstrap.php ----------------------------------------------------------------------------------------------------- -FOUND 4 ERRORS AFFECTING 4 LINES ----------------------------------------------------------------------------------------------------- - 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. - 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead ----------------------------------------------------------------------------------------------------- -PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY ----------------------------------------------------------------------------------------------------- - -Time: 639ms; Memory: 10MB -``` - -You can then use `./vendor/bin/phpcbf` to try to fix them - - -#### Symfony Var-Dumper - -another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small -functions. - -dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects -or arrays. - -dd() on the other hand is a function that dumps its parameters and then exits the php-script. - -you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. - -#### Composer scripts - -now we have a few commands that are available on the command line. i personally do not like to type complex commands -with lots of parameters by hand all the time, so i added a few lines to my composer.json: - -```json -"scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" -}, -``` - -that way i can just type "composer" followed by the command name in the root of my project. if i want to start the -php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the -time. - -You could also configure PhpStorm to automatically run these commands in the background and highlight the violations -directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my -flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. - -My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before -commiting and pushing the code. - -[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/app/data/pages/05-http.md b/app/data/pages/05-http.md deleted file mode 100644 index 6166214..0000000 --- a/app/data/pages/05-http.md +++ /dev/null @@ -1,124 +0,0 @@ -[<< previous](04-development-helpers.md) | [next >>](06-router.md) - -### HTTP - -PHP already has a few things built in to make working with HTTP easier. For example there are the -[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. - -These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, -if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, -then you will want a class with a nice object-oriented interface that you can use in your application instead. - -Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The -standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php -projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. - -As this is a widely adopted standard there are already several implementations available for us to use. I will choose -the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. - -Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a -[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. - -Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use -that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding -the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). - - -to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit -enter - -Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): - -```php -$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); -$response = new \Laminas\Diactoros\Response; -$response->getBody()->write('Hello World! '); -$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); -``` - -This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. - -In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the -write()-Method on that object. - - -To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: - -```php -echo $response->getBody(); -``` - -This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only -stores data. - -You can play around with the other methods of the Request object and take a look at its content with the dd() function. - -```php -dd($response) -``` - -Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot -be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which -creates a copy of the request object with the changed property and returns that clone: - -```php -$response = $response->withStatus(200); -$response = $response->withAddedHeader('Content-type', 'application/json'); -``` - -If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been -defined this way. - -But if you have been keeping attention you might argue that the following line should not work if the request object is -immutable. - -```php -$response->getBody()->write('Hello World!'); -``` - -The response-body implements a stream interface which is immutable for some reasons that are described in the -[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be -aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in -the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. -I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content -to a response object. - -```php -$body = $response->getBody(); -$body->write('Hello World!'); -$response = $response->withBody($body); -``` - -Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our -output-logic a little bit more. Replace the line that echos the response-body with the following: - -```php -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); -``` - -This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a -webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. - -Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. - -Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter - - -[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/app/data/pages/06-router.md b/app/data/pages/06-router.md deleted file mode 100644 index 6c39ae5..0000000 --- a/app/data/pages/06-router.md +++ /dev/null @@ -1,101 +0,0 @@ -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) - -### Router - -A router dispatches to different handlers depending on rules that you have set up. - -With your current setup it does not matter what URL is used to access the application, it will always result in the same -response. So let's fix that now. - -I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own -favorite package. - -Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) - -By now you know how to install Composer packages, so I will leave that to you. - -Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. - -```php -$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -switch ($routeInfo[0]) { - case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: - $response = (new \Laminas\Diactoros\Response)->withStatus(405); - $response->getBody()->write('Method not allowed'); - $response = $response->withStatus(405); - break; - case \FastRoute\Dispatcher::FOUND: - $handler = $routeInfo[1]; - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - /** @var \Psr\Http\Message\ResponseInterface $response */ - $response = call_user_func($handler, $request); - break; - case \FastRoute\Dispatcher::NOT_FOUND: - default: - $response = (new \Laminas\Diactoros\Response)->withStatus(404); - $response->getBody()->write('Not Found!'); - break; -} -``` - -In the first part of the code, you are registering the available routes for your application. In the second part, the -dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, -we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. -If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. - -This setup might work for really small applications, but once you start adding a few routes your bootstrap file will -quickly get cluttered. So let's move them out into a separate file. - -Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; - -```php -addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}; -``` - -Now let's rewrite the route dispatcher part to use the `Routes.php` file. - -```php -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); -``` - -This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. - -Of course we now need to add the 'config' folder to the configuration files of our -devhelpers so that they can scan that directory as well. - -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/app/data/pages/07-dispatching-to-a-class.md b/app/data/pages/07-dispatching-to-a-class.md deleted file mode 100644 index 0c961a4..0000000 --- a/app/data/pages/07-dispatching-to-a-class.md +++ /dev/null @@ -1,137 +0,0 @@ -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) - -### Dispatching to a Class - -In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). -MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to -learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) -and the followup posts. - -So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). - -We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other -common names are 'Controllers' or 'Actions'. - -Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. -In there, create a `Hello.php` file. - -```php -getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - } -} -``` - -You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) -that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is -fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement -it in some other parts of our application as well. In order to use that Interface we have to require it with composer: -'composer require psr/http-server-handler'. - -The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. -At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. - -Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: - -```php -return function(\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); - $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); -}; -``` - -Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared -the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing -the response to the one we defined for the second route. - -To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: - -```php -case \FastRoute\Dispatcher::FOUND: - $handler = new $routeInfo[1]; - if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { - throw new \Exception('Invalid Requesthandler'); - } - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - assert($response instanceof \Psr\Http\Message\ResponseInterface) - break; -``` - -So instead of just calling a method you are now instantiating an object and then calling the method on it. - -Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. - -And of course don't forget to commit your changes. - -Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still -generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for -example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still -have our whoopsie error-handler but i like to be more explicit in my control flow. - -In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new -Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and -InternalServerError. All three should extend phps Base Exception class. - -Here is my NotFound.php for example. - -```php - $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - break; - case Dispatcher::METHOD_NOT_ALLOWED: - throw new MethodNotAllowed; - - case Dispatcher::NOT_FOUND: - default: - throw new NotFound; - } -} catch (MethodNotAllowed) { - $response = (new Response)->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = (new Response)->withStatus(404); - $response->getBody()->write('Not Found'); -} catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); -} -``` - -Check if our code still works, try to trigger some errors, run phpstan and the fix command -and don't forget to commit your changes. - -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/app/data/pages/08-inversion-of-control.md b/app/data/pages/08-inversion-of-control.md deleted file mode 100644 index 21f4f23..0000000 --- a/app/data/pages/08-inversion-of-control.md +++ /dev/null @@ -1,54 +0,0 @@ -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) - -### Inversion of Control - -In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we -want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then -we would need to edit every one of our request handlers to call a different constructor of the class. - -The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that -instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done -with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). - -If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is -implemented, it will make sense. - -Change your `Hello` action to the following: - -```php -getAttribute('name', 'Stranger'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: - -```php -$handler = new $className($response); -``` - -Of course we need to also update all the other handlers. - -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/app/data/pages/09-dependency-injector.md b/app/data/pages/09-dependency-injector.md deleted file mode 100644 index 7f7c6a2..0000000 --- a/app/data/pages/09-dependency-injector.md +++ /dev/null @@ -1,213 +0,0 @@ -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) - -### Dependency Injector - -A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when -the class is instantiated. - -Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work -with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look -for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). - -I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) -out of the box. - -After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: - -```php -addDefinitions([ - \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), - \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), -]); - -return $builder->build(); -``` - -In this file we create a containerbuilder, add some definitions to it and return the container. -As the container supports autowiring we only need to define services where we want to use a specific implementation of -an interface. - -In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to -tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second -exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. - -Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), -as we will use that extensively. - -Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally -to create our Handler-Object - -```php -$container = require __DIR__ . '/../config/dependencies.php'; -assert($container instanceof \Psr\Container\ContainerInterface); - -$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); -assert($request instanceof \Psr\Http\Message\ServerRequestInterface); -``` - -The other part that has to be changed is the dispatching of the route. Before you had the following code: - -```php -$className = $routeInfo[1]; -$handler = new $className($response); -assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Change that to the following: - -```php -/** @var RequestHandlerInterface $handler */ -$className = $routeInfo[1]; -$handler = $container->get($className); -assert($handler instanceof RequestHandlerInterface); -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Make sure to use the container fetch the response object in the catch blocks as well: - -```php -} catch (MethodNotAllowed) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(404); - $response->getBody()->write('Not Found'); -} -``` - -Now all your controller constructor dependencies will be automatically resolved with PHP-DI. - -We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons -in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call -`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we -want to work with a different date in a test. - -Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation -that creates us a DateTimeImmutable object with the current date and time. - -src/Service/Time/Now.php: -```php -namespace Lubian\NoFramework\Service\Time; - -interface Now -{ - public function __invoke(): \DateTimeImmutable; -} -``` -src/Service/Time/SystemClockNow.php: -```php -namespace Lubian\NoFramework\Service\Time; - -final class SystemClockNow implements Now -{ - - public function __invoke(): \DateTimeImmutable - { - return new \DateTimeImmutable; - } -} -``` -If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and -update the handle-method to use the new class property: - -```php -getAttribute('name', 'Stranger'); - $nowAsString = ($this->now)()->format('H:i:s'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowAsString); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI -automatically figures out what classes are requested in the constructor and tries to create the objects needed. - -But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred -S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: - -```php - public function __construct( - private ResponseInterface $response, - private Now $now, - ) -``` - -When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation -should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: - -```php -\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), -``` - -we could also use the PHP-DI create method to delegate the object creation to the container implementation: -```php -\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), -``` - -this way the container can try to resolve any dependencies that the class might have internally, but prefer the other -method because we are not depending on this specific dependency injection implementation. - -Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are -requesting the Hello action. - -If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As -we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way -we wont get any warnings for this particular issue, but for any other issues we add to our code. - -Update the phpstan.neon file to include a "baseline" file: - -``` -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` - -if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and -ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just -'baseline' - -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/app/data/pages/10-invoker.md b/app/data/pages/10-invoker.md deleted file mode 100644 index 3033fae..0000000 --- a/app/data/pages/10-invoker.md +++ /dev/null @@ -1,102 +0,0 @@ -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) - -### Invoker - -Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the -one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long -with some services and not the whole Requestobject with all its various properties. - -If we take our Hello action for example we only need a response object, the time service and the 'name' information from -the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named -the class hello and it would be redundant to also call the the method hello. So an updated version of that class could -look like this: - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - string $name = 'Stranger', - ): ResponseInterface - { - $body = $this->response->getBody(); - $nowString = $now->get()->format('H:i:s'); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowString); - return $response - ->withBody($body) - ->withStatus(200); - } -} -``` - -It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short -closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the -rootpath of our application yet. - -```php -$r->addRoute('GET', '/hello[/{name}]', Hello::class); -$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); -$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -``` - -In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the -route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that -class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what -arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router -if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple -callable we would need to manually use reflection as well to resolve all the arguments. - -But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) -which handles all of that for us. - -After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo -switch section in the Bootstrap.php file to use the container->call() method: - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2]; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$response = $container->call($handler, $args); -``` - -Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. - -But by now you should know that I do not like to depend on specific implementations and the call method is not defined in -the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container -or any other implementation. - -Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific -container implementation so we could use it with any container that implements the ContainerInterface. And best of all -the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that -we could implement if we ever want to write our own implementation or we could write an adapter that uses a different -class that solves the same problem. - -But for now we are using the solution provided by PHP-DI. -So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2] ?? []; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$invoker = $container->get(InvokerInterface::class); -assert($invoker instanceof InvokerInterface); -$response = $invoker->call($handler, $args); -assert($response instanceof ResponseInterface); -``` - -Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) -by php, and even some more. - -But let us move on to something more fun and add some templating functionality to our application as we are trying to build -a website in the end. - -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/app/data/pages/11-templating.md b/app/data/pages/11-templating.md deleted file mode 100644 index 7bfe1aa..0000000 --- a/app/data/pages/11-templating.md +++ /dev/null @@ -1,236 +0,0 @@ -[<< previous](10-invoker.md) | [next >>](12-configuration.md) - -### Templating - -A template engine is not necessary with PHP because the language itself can take care of that. But it can make things -like escaping values easier. They also make it easier to draw a clear line between your application logic and the -template files which should only put your variables into the HTML code. - -A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please -also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. -Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. - -For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install -that package before you continue (`composer require mustache/mustache`). - -Another well known alternative would be [Twig](http://twig.sensiolabs.org/). - -Now please go and have a look at the source code of the -[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class -does not implement an interface. - -You could just type hint against the concrete class. But the problem with this approach is that you create tight -coupling. - -In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the -implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want -to add functionality to the engine. You can't do that without going back and changing all your code that is tightly -coupled. - -What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need -another implementation, you just implement that interface in your new class and inject the new class instead. - -Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). -This sounds a lot more complicated than it is, so just follow along. - -First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). -This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. -A class can implement multiple interfaces if necessary. - -So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a -new folder in your `src/` folder with the name `Template` where you can put all the template related things. - -In there create a new interface `Renderer.php` that looks like this: - -```php - $data */ - public function render(string $template, array $data = []) : string; -} -``` - -Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file -`MustacheRenderer.php` with the following content: - -```php -engine->render($template, $data); - } -} -``` - -As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple -and only fulfills the interface. - -Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know -which implementation he has to inject when you hint for the interface. Add this line: - -```php -[ - ... - \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) - ->constructor(new Mustache_Engine), -] -``` - -Now update the Hello.php class to require an implementation of our renderer interface -and use that to render a string using mustache syntax. - - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - Renderer $renderer, - string $name = 'Stranger', - ): ResponseInterface { - $body = $response->getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render( - 'Hello {{name}}, the time is {{now}}!', - $data, - ); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} -``` - -Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. -But what we want is template files, so let's go back and change that. - -To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the -`dependencies.php` file and add the following code: - -```php -[ - ... - Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), - Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), -] -``` - -We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. -Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have -to rename all the template files. - -To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the -dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader -in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object -we can then use it in the factory. - -In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: - -``` -

Hello World

-Hello {{ name }} -``` - -Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` - -Navigate to the hello page in your browser to make sure everything works. - -One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies -file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration -values. - -Lets create a 'Settings' class in our './src' Folder: - -```php -addDefinitions([ - Settings::class => fn () => require __DIR__ '/settings.php', - ResponseInterface::class => create(Response::class), - ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), - Renderer::class => fn (ME $me) => new Mustache($me), - MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), -]); - -return $builder->build(); -``` - - - -And as always, don't forget to commit your changes. - - -[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/app/data/pages/12-configuration.md b/app/data/pages/12-configuration.md deleted file mode 100644 index 4b60c19..0000000 --- a/app/data/pages/12-configuration.md +++ /dev/null @@ -1,200 +0,0 @@ -[<< previous](11-templating.md) | [next >>](13-refactoring.md) - -### Configuration - -In the last chapter we added some more definitions to our dependencies.php in that definitions -we needed to pass quite a few configuration settings and filesystem strings to the constructors -of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. - -As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. - -As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. - -Lets create a 'Settings' class in our './src' Folder: - -```php -filePath; - } -} -``` - -If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files -and craft a settings object from them. - -As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another -factory for our Container because everyone should have a Friend :) - -```php -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn () => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} -``` - -For this to work we need to change our dependencies.php file to just return the array of definitions: -And here we can instantly use the Settings object to create our template engine. - -```php - fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), -]; -``` - -Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: - -```php -... -error_reporting(E_ALL); - -$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); -$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); -... -``` - -Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. - -[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/app/data/pages/13-refactoring.md b/app/data/pages/13-refactoring.md deleted file mode 100644 index 6dbbb8d..0000000 --- a/app/data/pages/13-refactoring.md +++ /dev/null @@ -1,373 +0,0 @@ -[<< previous](12-configuration.md) | [next >>](14-middleware.md) - -### Refactoring - -By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no -reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. -After all the bootstrap file should just set up the classes needed for the handling logic and execute them. - -At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. -As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the -method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non -static and extracted an interface. - -'./src/Http/Emitter.php' -```php -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - echo $response->getBody(); - } -} -``` -After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following -code in the Bootstrap.php to emit the response: - -```php -/** @var Emitter $emitter */ -$emitter = $container->get(Emitter::class); -$emitter->emit($response); -``` - -If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily -write an adapter that implements your emitter interface and wraps that more advanced emitter - -Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and -calling the routerhandler that in the passes the request to a function and gets the response. - -For this to steps to be seperated we are going to create two more classes: -1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object -2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the - requestobject, fetches the correct object from the container and calls it to create a response. - -Lets create the HandlerInterface first: - -```php -getAttribute($this->routeAttributeName, false); - assert($handler !== false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} - -``` - -We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. -The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler -as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in -the next chapter. - -```php -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} -``` - -Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. -```php -[ - '...', - Emitter::class => fn (BasicEmitter $e) => $e, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, -], -``` - -And then we can update our Bootstrap.php to fetch all the services and let them handle the request. - -```php -... -$routeMiddleWare = $container->get(MiddlewareInterface::class); -assert($routeMiddleWare instanceof MiddlewareInterface); -$handler = $container->get(RoutedRequestHandler::class); -assert($handler instanceof RequestHandlerInterface); -$emitter = $container->get(Emitter::class); -assert($emitter instanceof Emitter); - -$request = $container->get(ServerRequestInterface::class); -assert($request instanceof ServerRequestInterface); - -$response = $routeMiddleWare->process($request, $handler); -$emitter->emit($response); -``` -Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of -code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). - -So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. - -I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class -should require to function properly. - -* A RequestFactory - We want our Kernel to be able to build the request itself -* An Emitter - Without an Emitter we will not be able to send the response to the client -* RouteMiddleware - To decore the request with the correct handler for the requested route -* RequestHandler - To delegate the request to the correct funtion that creates the response - -As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface -to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our -interface: - -```php -factory::fromGlobals(); - } - - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} -``` - -For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: - -```php -routeMiddleware->process($request, $this->handler); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} - -``` - -We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines - -```php -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); -``` - -You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking -at the Whoops output and adding the needed definitions to the dependencies.php file. - -And as always, don't forget to commit your changes. - -[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/app/data/pages/14-middleware.md b/app/data/pages/14-middleware.md deleted file mode 100644 index 81f82a5..0000000 --- a/app/data/pages/14-middleware.md +++ /dev/null @@ -1,303 +0,0 @@ -[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) - -### Middleware - -In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain -a bit more about what this interface does and why it is used in many applications. - -The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets -passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all -the middlewars to the client/emitter. - -So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the -response after it gets created by our handlers. - -So lets take a look at the middleware and the requesthandler interfaces - -```php -interface MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; -} - -interface RequestHandlerInterface -{ - public function handle(ServerRequestInterface $request): ResponseInterface; -} -``` - -The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a -requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the -response. - -But the middleware could just ignore the handler and produce a response on its own as the interface just requires us -to produce a response. - -A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users -that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the -database. - -In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates -the request with an 'isAuthenticated' attribute. - -If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that -response is not already cached, than we let the handler create the response and store that in the cache for a few -seconds - -```php -interface CacheInterface -{ - public function get(string $key, callable $resolver, int $ttl): mixed; -} -``` - -The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one -defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses -the callable to produce the value and stores it for the time specified in ttl. - -so lets write our caching middleware: - -```php -final class CachingMiddleware implements MiddlewareInterface -{ - public function __construct(private CacheInterface $cache){} - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { - $key = $request->getUri()->getPath(); - return $this->cache->get($key, fn() => $handler->handle($request), 10); - } - return $handler->handle($request); - } -} -``` - -we can also modify the response after it has been created by our application, for example we could implement a gzip -middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser -know that our application is used to serve dank memes: - -```php -final class DankMemeMiddleware implements MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - $response = $handler->handle($request); - return $response->withAddedHeader('Meme', 'Dank'); - } -} -``` - -but for our application we are going to just add two external middlewares: - -* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. -* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware - -```bash -composer require middlewares/trailing-slash -composer require middlewares/whoops -``` - -The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the -application as well as the middleware stack. - -Our desired request -> response flow looks something like this: - - Client - | ^ - v | - Kernel - | ^ - v | - Whoops Middleware - | ^ - v | - TrailingSlash - | ^ - v | - Routing - | ^ - v | - ContainerResolver - | ^ - v | - Controller/Action - -As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every -middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. - -```php -interface Pipeline -{ - public function dispatch(ServerRequestInterface $request): ResponseInterface; -} -``` - -And our implementation looks something like this: - -```php - $middlewares - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} -``` - -Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code -that should produce our response. - -In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument -and store that itself as the current tip. - -There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) - -Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline -Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline - -```php -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} -``` - -And configure the container to use the Factory to create the Pipeline: - -```php - ..., - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - ... -``` -And of course a new file called middlewares.php in our config folder: -```php -pipeline->dispatch($request); -} -``` - -Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our -pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a -dependency container, if you want you can do that as well. - -**A quick note about docblocks:** You might have noticed, that I rarely add docblocks to my the code in the examples, and -when I do it seems kind of random. My philosophy is that I only add docblocks when there is no way to automatically get -the exact type from the code itself. For me docblocks only serve two purposes: help my IDE to understand what it choices -it has for code completion and to help the static analysis to better understand the code. There is a great blogpost -about the [cost and value of DocBlocks](https://localheinz.com/blog/2018/05/06/cost-and-value-of-docblocks/), although it -is written in 2018 at a time before PHP 7.4 was around everything written there is still valid today. - -[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) diff --git a/app/data/pages/15-adding-content.md b/app/data/pages/15-adding-content.md deleted file mode 100644 index 64562fa..0000000 --- a/app/data/pages/15-adding-content.md +++ /dev/null @@ -1,253 +0,0 @@ -[<< previous](14-middleware.md) | [next >>](16-data-repository.md) - -### Adding Content - -By now we did not really display anything but some examples to in our application and it is now time to make our app -display some content. For example we could our app be able to display the Markdown files used in this tutorial as -nicely rendered HTML Pages that can be viewed in the browser instead of the editor you are using. - -So lets start by copying the markdown files to our app directory. I have created a new folder 'data/pages' and placed all -the markdown files in there. - -Next we need a markdown parser, a pretty simple one is [Parsedown](https://parsedown.org/), if you want more features -you could also use the [Commonmark parser](https://commonmark.thephpleague.com/). I will choose Parsedown here, but you -can use whatever you like. - -After installing Parsedown lets write a Markdownparser interface and an implementation using parsedown. - -We only need one function that receives a string of Markdown and returns the HTML represantion (as a string as well). - - - -```php -parser->parse($markdown); - } -} -``` - -We could now use the ParsedownRender class directly in our actions by typehinting the classname as an argument to the -constructor or a method, but as we always want to rely on an interface instead of an implementation we need to define -the the ParsedownRenderer as the correct implementation for the MarkdownRenderer interface in the dependencies file: - -```php -... - \Lubian\NoFramework\Template\MarkdownParser::class => fn(\Lubian\NoFramework\Template\ParsedownParser $p) => $p, -... -``` - -You can test that in our "Other.php" action and try out if the Parser works and is able to render Markdown to HTML: - -```php -public function someFunctionName(ResponseInterface $response, MarkdownParser $parser): ResponseInterface -{ - $html = $parser->parse('This *works* **too!**'); - $response->getBody()->write($html); - return $response->withStatus(200); -} -``` - -But we want to display complete Pages written in Markdown, it would also be neat to be able to display a list of all -available pages. For that we need a few things: - -Firstly we need two new Templates, one for the list of the Pages, and the second one for displaying a single pages -content. Create a new folder in `templates/page` with to files: - -`templates/page/list.html` -```html - - - - - Pages - - - -
- -
- - -``` - -This template iterates over a provided array of pages, each element consists of the two properties: an id and a title, -those are simply displayed using an unordered list. - -`templates/page/show.html` -```html - - - - - {{title}} - - - - - - -
- {{{content}}} -
- - -``` - -The second templates displays a single rendered markdown page. As data it expects the title and the content as array. -I used an extra bracket for the content ```{{{content}}}``` so that the Mustache-Renderer does not escape the provided -html and thereby destroys the the parsed markdown. - -You might have spotted that I added [Pico.css](https://picocss.com/) which is just a very small css framework to make the -pages a little bit nicer to look at. It mostly provides some typography styles that work great with rendered Markdown, -but you can leave that out or use any other css framework you like. There is also some Javascript that adds syntax -highlighting to the code. - -After you have taken care of the templating side we can now create an new Action class with two methods to display use -our markdown files and the templates to create the pages. As we have two templates I propose to use Two methods in our -Action: -`src/Action/Page.php` -```php -function show(string $name): \Psr\Http\Message\ResponseInterface; -function list(): \Psr\Http\Message\ResponseInterface; -``` - -Lets define two routes. `/page` should display the overview of all pages, and if the add the name of chapter to the -route, `/page/adding-content` for example, the show action should be called with the name as a variable: - -`config/routes.php` -```php -$r->addRoute('GET', '/page', [Page::class, 'list']); -$r->addRoute('GET', '/page/{page}', [Page::class, 'show']); -``` - -Here is my Implementation. If have added a little regex replacement in the show method that replaces the links to the -next and previous chapter so that it works with our routing configuration. - -`src/Action/Page.php` -```php -getPages(), - fn (string $filename) => str_contains($filename, $page) - ) - )[0]; - $markdown = file_get_contents($page); - - // fix the next and previous buttons to work with our routing - $markdown = preg_replace('/\(\d\d-/m', '(', $markdown); - $markdown = str_replace('.md)', ')', $markdown); - - $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); - $data = [ - 'title' => substr($page, 3), - 'content' => $this->parser->parse($markdown), - ]; - $html = $this->renderer->render('page/show', $data); - $this->response->getBody()->write($html); - return $this->response; - } - - public function list(): ResponseInterface - { - $pages = array_map(function (string $page) { - $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); - return [ - 'id' => substr($page, 0, 2), - 'title' => substr($page, 3), - ]; - }, $this->getPages()); - $html = $this->renderer->render('page/list', ['pages' => $pages]); - $this->response->getBody()->write($html); - return $this->response; - } - - /** - * @return string[] - */ - private function getPages(): array - { - $files = glob($this->pagesPath . '*.md'); - if ($files === false) { - throw new InternalServerError('cannot read pages'); - } - return $files; - } -} -``` - -You can now navigate your Browser to [localhost:1234/page][http://localhost:1234/page] and try out if everything works. - -Of course this code is far from looking good. We heavily rely on the pages being files in the filesystem, and the action -should never be aware of the filesystem in the first place, also we have a lot of string replacements and other repetetive -code in the file. And phpstan is gonna scream at us a lot, but if we rewrite the code to satisfy all the checks we would -add even more lines to that simple class, so lets move on to the next chapter where we move all the logic to seperate -classes following our holy SOLID principles :) - - -[<< previous](14-middleware.md) | [next >>](16-data-repository.md) diff --git a/app/data/pages/16-data-repository.md b/app/data/pages/16-data-repository.md deleted file mode 100644 index d9a3218..0000000 --- a/app/data/pages/16-data-repository.md +++ /dev/null @@ -1,265 +0,0 @@ -[<< previous](15-adding-content.md) | [next >>](17-performance.md) - -## Data Repository - -At the end of the last chapter I mentioned being unhappy with our Pages action, because there is to much stuff happening -there. We are firstly receiving some Arguments, then we are using those to query the filesytem for the given page, -loading the specific file from the filesystem, rendering the markdown, passing the markdown to the template renderer, -adding the resulting html to the response and then returning the response. - -In order to make our pageaction independent from the filesystem and move the code that is responsible for reading the -files -to a better place I want to introduce -the [Repository Pattern](https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html). - -I want to start by creating a class that represents the Data that is included in a page so that. For now I can spot -three -distrinct attributes. - -* the ID (or chapternumber) -* the title (or name) -* the content - -Currently all those properties are always available, but we might later be able to create new pages and store them, but -at that point in time we are not yet aware of the new available ID, so we should leave that property nullable. This -allows -us to create an object without an id and let the code that actually saves the object to a persistant store define a -valid -id on saving. - -Lets create an new Namespace called `Model` and put a `MarkdownPage.php` class in there: - -```php -dataPath . '*.md'); - if ($files === false) { - throw new InternalServerError('cannot read pages'); - } - return array_map(function (string $filename) { - $content = file_get_contents($filename); - if ($content === false) { - throw new InternalServerError('cannot read pages'); - } - $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); - return new MarkdownPage( - (int) substr($idAndTitle, 0, 2), - substr($idAndTitle, 3), - $content - ); - }, $files); - } - - public function byName(string $name): MarkdownPage - { - $pages = array_values( - array_filter( - $this->all(), - fn (MarkdownPage $p) => $p->title === $name, - ) - ); - - if (count($pages) !== 1) { - throw new NotFound; - } - - return $pages[0]; - } -} -``` - -With that in place we need to add the required `$pagesPath` to our settings class and add specify that in our -configuration. - -`src/Settings.php` - -```php -final class Settings -{ - public function __construct( - public readonly string $environment, - public readonly string $dependenciesFile, - public readonly string $middlewaresFile, - public readonly string $templateDir, - public readonly string $templateExtension, - public readonly string $pagesPath, - ) { - } -} -``` - -`config/settings.php` - -```php -return new Settings( - environment: 'prod', - dependenciesFile: __DIR__ . '/dependencies.php', - middlewaresFile: __DIR__ . '/middlewares.php', - templateDir: __DIR__ . '/../templates', - templateExtension: '.html', - pagesPath: __DIR__ . '/../data/pages/', -); -``` - -Of course we need to define the correct implementation for the container to choose when we are requesting the Repository -interface: -`conf/dependencies.php` - -```php -MarkdownPageRepo::class => fn (FileSystemMarkdownPageRepo $r) => $r, -FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), -``` - -Now you can request the MarkdownPageRepo Interface in your page action and use the defined functions to get the -MarkdownPage -Objects. My `src/Action/Page.php` looks like this now: - -```php -repo->byName($page); - - // fix the next and previous buttons to work with our routing - $content = preg_replace('/\(\d\d-/m', '(', $page->content); - assert(is_string($content)); - $content = str_replace('.md)', ')', $content); - - $data = [ - 'title' => $page->title, - 'content' => $this->parser->parse($content), - ]; - - $html = $this->renderer->render('page/show', $data); - $this->response->getBody()->write($html); - return $this->response; - } - - public function list(): ResponseInterface - { - $pages = array_map(function (MarkdownPage $page) { - return [ - 'id' => $page->id, - 'title' => $page->content, - ]; - }, $this->repo->all()); - - $html = $this->renderer->render('page/list', ['pages' => $pages]); - $this->response->getBody()->write($html); - return $this->response; - } -} -``` - -Check the page in your browser if everything still works, don't forget to run phpstan and the others fixers before -committing your changes and moving on to the next chapter. - -[<< previous](15-adding-content.md) | [next >>](17-performance.md) diff --git a/app/data/pages/17-performance.md b/app/data/pages/17-performance.md deleted file mode 100644 index c83c7d5..0000000 --- a/app/data/pages/17-performance.md +++ /dev/null @@ -1,43 +0,0 @@ -[<< previous](16-data-repository.md) | [next >>](18-caching.md) - -## Autoloading performance - -Although our application is still very small and you should not really experience any performance issues right now, -there are still some things we can already consider and take a look at. If I check the network tab in my browser it takes -about 90-400ms to show a simple rendered markdownpage, with is sort of ok but in my opinion way to long as we are not -really doing anything and do not connect to any external services. Mostly we are just reading around 16 markdown files, -a template, some config files here and there and parse some markdown. So that should not really take that long. - -The problem is, that we heavily rely on autoloading for all our class files, in the `src` folder. And there are also -quite a lot of other files in composers `vendor` directory. To understand while this is becomming we should make -ourselves familiar with how [autoloading in php](https://www.php.net/manual/en/language.oop5.autoload.php) works. - -The basic idea is, that every class that php encounters has to be loaded from somewhere in the filesystem, we could -just require the files manually but that is tedious, unflexible and can often cause errors. - -The problem we are now facing is that the composer autoloader has some rules to determine from where in the filesystem -a class definition might be placed, then the autoloader tries to locate a file by the namespace and classname and if it -exists includes that file. - -If we only have a handfull of classes that does not take a lot of time, but as we are growing with our application this -easily takes longer than necesery, but fortunately composer has some options to speed up the class loading. - -Take a few minutes to read the documentation about [composer autoloader optimization](https://getcomposer.org/doc/articles/autoloader-optimization.md) - -You can try all 3 levels of optimizations, but we are going to stick with the first one for now, so lets create an -optimized classmap. - -`composer dump-autoload -o` - -After composer has finished you can start the devserver again with `composer serve` and take a look at the network tab -in your browsers devtools. - -In my case the response time falls down to under an average of 30ms with some spikes in between, but all in all it looks really good. -You can also try out the different optimization levels and see if you can spot any differences. - -Although the composer manual states not to use the optimtization in a dev environment I personally have not encountered -any errors with the first level of optimizations, so we can use that level here. If you add the line from the documentation -to your `composer.json` so that the autoloader gets optimized everytime we install new packages. - - -[<< previous](16-data-repository.md) | [next >>](18-caching.md) diff --git a/app/data/pages/18-caching.md b/app/data/pages/18-caching.md deleted file mode 100644 index 42e9cb1..0000000 --- a/app/data/pages/18-caching.md +++ /dev/null @@ -1,252 +0,0 @@ -[<< previous](17-performance.md) | [next >>](19-database.md) - -**DISClAIMER** I do not really have a lot of experience when it comes to caching, so this chapter is mostly some random -thoughts and ideas I wanted to explore when writing this tutorial, you should definitely take everything that is being -said here with caution and try to read up on some other sources. But that holds true for the whole tutorial anyway :) - -## Caching - -In the last chapter we greatly improved the perfomance for the lookup of all our classfiles, but currently we do not -have any real bottlenecks in our application like complex queries. - -But in a real application we are going to execute some really heavy and time intensive database queries that can take -quite a while to be completed. - -We can simulate that by adding a simple delay in our `FileSystemMarkdownPageRepo`. - -```php - return array_map(function (string $filename) { - usleep(rand(100, 400) * 1000); - $content = file_get_contents($filename); - if ($content === false) { - throw new InternalServerError('cannot read pages'); - } - $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); - return new MarkdownPage( - (int) substr($idAndTitle, 0, 2), - substr($idAndTitle, 3), - $content - ); -}); -``` - -Here I added a function that pauses the scripts execution for a random time between 100 and 400ms for every markdownpage -in every call of the `all()` method. - -If you open any page or even the listAction in you browser you will see, that it takes quite a time to render that page. -Although this is a silly example we do not really need to query the database on every request, so lets add a way to cache -the database results between requests. - -The PHP-Community has already adressed the issue of having easy to use access to cache libraries, there is the -[PSR-6 Caching Interface](https://www.php-fig.org/psr/psr-6) which gives us easy access to many different implementations, -then there is also a much simpler [PSR-16 Simple Cache](https://www.php-fig.org/psr/psr-16) which makes the use even more -easy, and most Caching Libraries implement Both interfaces anyway. You would think that this is more than enough solutions -to satisfy all the Caching needs around, but the Symfony People decided that Caching should be even simpler and easier -to use and defined their own [Interface](https://symfony.com/doc/current/components/cache.html#cache-component-contracts) -which only needs two methods. You should definitely take a look at the linked documentation as it really blew my mind -when I first encountered it. - -The basic idea is that you provide a callback that computes the requested value. The Cache implementation then checks -if it already has the value stored somewhere and if it doesnt it just executes the callback and stores the value for -future calls. - -It is really simple and great to use. In a real world application you should definitely use that or a PSR-16 implementation -but for this tutorial I wanted to roll out my own solution, so here we go. - -As always we are going to define an interface first, I am going to call it EasyCache and place it in the `Service/Cache` -namespace. I will require only one method which is base on the Symfony Cache Contract, and hast a key, a callback, and -the duration that the item should be cached as arguments. - -```php -cache->get( - $key, - fn () => $this->repo->all(), - 300 - ); - } - - public function byName(string $name): MarkdownPage - { - $key = base64_encode(self::class . 'byName' . $name); - return $this->cache->get( - $key, - fn () => $this->repo->byName($name), - 300 - ); - } -} -``` - -This simple wrapper just requires an EasyCache implementation and a MarkdownPageRepo in the constructor and uses them -to cache all queries for 5 minutes. The beauty is that we are not dependent on any implementation here, so we can switch -out the Repository or the Cache at any point down the road if we want to. - -In order to use that we need to update our `config/dependencies.php` to add an alias for the EasyCache interface as well -as defining our CachedMarkdownPageRepo as implementation for the MarkdownPageRepo interface: - -```php -MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, -EasyCache::class => fn (ApcuCache $c) => $c, -``` - -If we try to access our webpage now, we are getting an error, as PHP-DI has detected a circular dependency that cannot -be autowired. - -The Problem is that our CachedMarkdownPageRepo ist defined as the implementation for the MarkdownPageRepo, but it also -requires that exact interface as a dependency. To resolve this issue we need to manually tell the container how to build -the CachedMarkdownPageRepo by adding another line to the `config/dependencies.php` file: - -```php -CachedMarkdownPageRepo::class => fn (EasyCache $c, FileSystemMarkdownPageRepo $r) => new CachedMarkdownPageRepo($c, $r), -``` - -Here we explicitly require the FileSystemMarkdownPageRepo and us that to create the CachedMarkdownPageRepo object. - -When you now navigate to the pages list or to a specific page the first load should take a while (because of our added delay) -but the following request should be answered blazingly fast. - -Before moving on to the next chapter we can take the caching approach even further, in the middleware chapter I talked -about a simple CachingMiddleware that caches all the GET-Request for some seconds, as they should not change that often, -and we can bypass most of our application logic if we just complelety cache away the responses our application generates, -and return them quite early in our Middleware-Pipeline befor the router gets called, or the invoker calls the action, -which itself uses some other services to fetch all the needed data. - -We will introduce a new `Middleware` namespace to place our `Cache.php` middleware: -```php -getMethod() !== 'GET') { - return $handler->handle($request); - } - $keyHash = base64_encode($request->getUri()->getPath()); - $result = $this->cache->get( - $keyHash, - fn () => Serializer::toString($handler->handle($request)), - 300 - ); - return Serializer::fromString($result); - } -} -``` - -The code is quite straight forward, but you might be confused by the Responseserializer I have added here, we need this -because the response body is a stream object, which doesnt always gets serialized correctly, therefore I use a class from -the laminas project to to all the heavy lifting for us. - -We need to add the now middleware to the `config/middlewares.php` file. - -```php ->](19-database.md) diff --git a/app/phpstan-baseline.neon b/app/phpstan-baseline.neon deleted file mode 100644 index 61697a1..0000000 --- a/app/phpstan-baseline.neon +++ /dev/null @@ -1,7 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" - count: 1 - path: src/Http/InvokerRoutedHandler.php - diff --git a/app/phpstan.neon b/app/phpstan.neon deleted file mode 100644 index 2eac45a..0000000 --- a/app/phpstan.neon +++ /dev/null @@ -1,8 +0,0 @@ -includes: - - phpstan-baseline.neon - -parameters: - level: max - paths: - - src - - config \ No newline at end of file diff --git a/app/public/css/spectre-exp.min.css b/app/public/css/spectre-exp.min.css deleted file mode 100644 index d313774..0000000 --- a/app/public/css/spectre-exp.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/app/public/css/spectre-icons.min.css b/app/public/css/spectre-icons.min.css deleted file mode 100644 index 0276f7b..0000000 --- a/app/public/css/spectre-icons.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/app/public/css/spectre.min.css b/app/public/css/spectre.min.css deleted file mode 100644 index 0fe23d9..0000000 --- a/app/public/css/spectre.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/app/public/index.php b/app/public/index.php deleted file mode 100644 index 32f5eb3..0000000 --- a/app/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render('hello', $data); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/app/src/Action/Other.php b/app/src/Action/Other.php deleted file mode 100644 index da9ceaf..0000000 --- a/app/src/Action/Other.php +++ /dev/null @@ -1,16 +0,0 @@ -parse('This *works* **too!**'); - $response->getBody()->write($html); - return $response->withStatus(200); - } -} diff --git a/app/src/Action/Page.php b/app/src/Action/Page.php deleted file mode 100644 index 96696e4..0000000 --- a/app/src/Action/Page.php +++ /dev/null @@ -1,60 +0,0 @@ -repo->byName($page); - - // fix the next and previous buttons to work with our routing - $content = preg_replace('/\(\d\d-/m', '(', $page->content); - assert(is_string($content)); - $content = str_replace('.md)', ')', $content); - - $data = [ - 'title' => $page->title, - 'content' => $this->parser->parse($content), - ]; - - $html = $this->renderer->render('page/show', $data); - $this->response->getBody()->write($html); - return $this->response; - } - - public function list(): ResponseInterface - { - $pages = array_map(function (MarkdownPage $page) { - return [ - 'id' => $page->id, - 'title' => $page->title, - ]; - }, $this->repo->all()); - - $html = $this->renderer->render('page/list', ['pages' => $pages]); - $this->response->getBody()->write($html); - return $this->response; - } -} diff --git a/app/src/Bootstrap.php b/app/src/Bootstrap.php deleted file mode 100644 index 3abc2e5..0000000 --- a/app/src/Bootstrap.php +++ /dev/null @@ -1,40 +0,0 @@ -getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); diff --git a/app/src/Exception/InternalServerError.php b/app/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/app/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ -factory::fromGlobals(); - } - - /** - * @param UriInterface|string $uri - * @param array $serverParams - */ - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} diff --git a/app/src/Factory/FileSystemSettingsProvider.php b/app/src/Factory/FileSystemSettingsProvider.php deleted file mode 100644 index f071078..0000000 --- a/app/src/Factory/FileSystemSettingsProvider.php +++ /dev/null @@ -1,22 +0,0 @@ -filePath; - assert($settings instanceof Settings); - return $settings; - } -} diff --git a/app/src/Factory/PipelineProvider.php b/app/src/Factory/PipelineProvider.php deleted file mode 100644 index 77738f8..0000000 --- a/app/src/Factory/PipelineProvider.php +++ /dev/null @@ -1,25 +0,0 @@ -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} diff --git a/app/src/Factory/RequestFactory.php b/app/src/Factory/RequestFactory.php deleted file mode 100644 index 2b17abc..0000000 --- a/app/src/Factory/RequestFactory.php +++ /dev/null @@ -1,11 +0,0 @@ -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = $settings; - $builder->addDefinitions($dependencies); - // $builder->enableCompilation('/tmp'); - return $builder->build(); - } -} diff --git a/app/src/Factory/SettingsProvider.php b/app/src/Factory/SettingsProvider.php deleted file mode 100644 index ce1c5f0..0000000 --- a/app/src/Factory/SettingsProvider.php +++ /dev/null @@ -1,10 +0,0 @@ -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - - echo $response->getBody(); - } -} diff --git a/app/src/Http/ContainerPipeline.php b/app/src/Http/ContainerPipeline.php deleted file mode 100644 index 816cedd..0000000 --- a/app/src/Http/ContainerPipeline.php +++ /dev/null @@ -1,82 +0,0 @@ - $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} diff --git a/app/src/Http/Emitter.php b/app/src/Http/Emitter.php deleted file mode 100644 index ce4c035..0000000 --- a/app/src/Http/Emitter.php +++ /dev/null @@ -1,10 +0,0 @@ -getAttribute($this->routeAttributeName, false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} diff --git a/app/src/Http/Pipeline.php b/app/src/Http/Pipeline.php deleted file mode 100644 index 1a9dcda..0000000 --- a/app/src/Http/Pipeline.php +++ /dev/null @@ -1,11 +0,0 @@ -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} diff --git a/app/src/Http/RoutedRequestHandler.php b/app/src/Http/RoutedRequestHandler.php deleted file mode 100644 index a7407c9..0000000 --- a/app/src/Http/RoutedRequestHandler.php +++ /dev/null @@ -1,10 +0,0 @@ -pipeline->dispatch($request); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} diff --git a/app/src/Middleware/Cache.php b/app/src/Middleware/Cache.php deleted file mode 100644 index 8460761..0000000 --- a/app/src/Middleware/Cache.php +++ /dev/null @@ -1,38 +0,0 @@ -getMethod() !== 'GET') { - return $handler->handle($request); - } - $keyHash = base64_encode($request->getUri()->getPath()); - $result = $this->cache->get( - $keyHash, - fn () => $this->serializer::toString($handler->handle($request)), - 300 - ); - assert(is_string($result)); - return $this->serializer::fromString($result); - } -} diff --git a/app/src/Model/MarkdownPage.php b/app/src/Model/MarkdownPage.php deleted file mode 100644 index df244fd..0000000 --- a/app/src/Model/MarkdownPage.php +++ /dev/null @@ -1,13 +0,0 @@ -cache->get( - $key, - fn () => $this->repo->all(), - 300 - ); - assert(is_array($result)); - foreach ($result as $page) { - assert($page instanceof MarkdownPage); - } - return $result; - } - - public function byName(string $name): MarkdownPage - { - $key = base64_encode(self::class . 'byName' . $name); - $result = $this->cache->get( - $key, - fn () => $this->repo->byName($name), - 300 - ); - assert($result instanceof MarkdownPage); - return $result; - } -} diff --git a/app/src/Repository/FileSystemMarkdownPageRepo.php b/app/src/Repository/FileSystemMarkdownPageRepo.php deleted file mode 100644 index cca350e..0000000 --- a/app/src/Repository/FileSystemMarkdownPageRepo.php +++ /dev/null @@ -1,61 +0,0 @@ -dataPath . '*.md'); - if ($files === false) { - throw new InternalServerError('cannot read pages'); - } - return array_map(function (string $filename) { - $content = file_get_contents($filename); - if ($content === false) { - throw new InternalServerError('cannot read pages'); - } - $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); - return new MarkdownPage( - (int) substr($idAndTitle, 0, 2), - substr($idAndTitle, 3), - $content - ); - }, $files); - } - - public function byName(string $name): MarkdownPage - { - $pages = array_values( - array_filter( - $this->all(), - fn (MarkdownPage $p) => $p->title === $name, - ) - ); - - if (count($pages) !== 1) { - throw new NotFound; - } - - return $pages[0]; - } -} diff --git a/app/src/Repository/MarkdownPageRepo.php b/app/src/Repository/MarkdownPageRepo.php deleted file mode 100644 index 0792d32..0000000 --- a/app/src/Repository/MarkdownPageRepo.php +++ /dev/null @@ -1,15 +0,0 @@ -engine->render($template, $data); - } -} diff --git a/app/src/Template/ParsedownParser.php b/app/src/Template/ParsedownParser.php deleted file mode 100644 index 2ffd287..0000000 --- a/app/src/Template/ParsedownParser.php +++ /dev/null @@ -1,17 +0,0 @@ -parser->parse($markdown); - } -} diff --git a/app/src/Template/Renderer.php b/app/src/Template/Renderer.php deleted file mode 100644 index ff916ed..0000000 --- a/app/src/Template/Renderer.php +++ /dev/null @@ -1,11 +0,0 @@ - $data - */ - public function render(string $template, array $data = []): string; -} diff --git a/app/templates/hello.html b/app/templates/hello.html deleted file mode 100644 index 15a4cd2..0000000 --- a/app/templates/hello.html +++ /dev/null @@ -1,6 +0,0 @@ -{{> partials/head }} -
-

Hello {{name}}

-

The time is {{now}}

-
-{{> partials/foot }} diff --git a/app/templates/page.html b/app/templates/page.html deleted file mode 100644 index c3c5284..0000000 --- a/app/templates/page.html +++ /dev/null @@ -1,5 +0,0 @@ -{{> partials/head }} -
- {{{content}}} -
-{{> partials/foot }} diff --git a/app/templates/page/list.html b/app/templates/page/list.html deleted file mode 100644 index bf42348..0000000 --- a/app/templates/page/list.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Pages - - - -
- -
- - \ No newline at end of file diff --git a/app/templates/page/show.html b/app/templates/page/show.html deleted file mode 100644 index abe295e..0000000 --- a/app/templates/page/show.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - {{title}} - - - - - - -
- {{{content}}} -
- - \ No newline at end of file diff --git a/app/templates/pagelist.html b/app/templates/pagelist.html deleted file mode 100644 index 538e2c4..0000000 --- a/app/templates/pagelist.html +++ /dev/null @@ -1,11 +0,0 @@ -{{> partials/head }} -
- -
-{{> partials/foot }} diff --git a/app/templates/partials/foot.html b/app/templates/partials/foot.html deleted file mode 100644 index 17c7245..0000000 --- a/app/templates/partials/foot.html +++ /dev/null @@ -1,3 +0,0 @@ -
- - \ No newline at end of file diff --git a/app/templates/partials/head.html b/app/templates/partials/head.html deleted file mode 100644 index 421d387..0000000 --- a/app/templates/partials/head.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - No Framework: {{title}} - - - - - -
diff --git a/implementation/01-front-controller/public/index.php b/implementation/01-front-controller/public/index.php deleted file mode 100644 index 43d37a2..0000000 --- a/implementation/01-front-controller/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "phpstan/phpstan", - "version": "1.4.10", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.4.10" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-14T10:25:45+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/03-error-handler/public/index.php b/implementation/03-error-handler/public/index.php deleted file mode 100644 index 43d37a2..0000000 --- a/implementation/03-error-handler/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (\Throwable $e) { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -throw new \Exception("Ooooopsie"); diff --git a/implementation/04-dev-helpers/.php-cs-fixer.php b/implementation/04-dev-helpers/.php-cs-fixer.php deleted file mode 100644 index 3db1195..0000000 --- a/implementation/04-dev-helpers/.php-cs-fixer.php +++ /dev/null @@ -1,41 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'linebreak_after_opening_tag' => true, - 'native_constant_invocation' => true, - 'native_function_invocation' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - 'ordered_imports' => [ - 'sort_algorithm' => 'alpha', - 'imports_order' => [ - 'const', - 'class', - 'function', - ], - ], - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in(__DIR__ . '/src') - ); \ No newline at end of file diff --git a/implementation/04-dev-helpers/composer.json b/implementation/04-dev-helpers/composer.json deleted file mode 100644 index 995d677..0000000 --- a/implementation/04-dev-helpers/composer.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "scripts": { - "serve": "php -S localhost:1234 -t ./public", - "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", - "phpstan": "./vendor/bin/phpstan analyze", - "style": "./vendor/bin/php-cs-fixer fix" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "php-cs-fixer/shim": "^3.7", - "symfony/var-dumper": "^6.0" - } -} diff --git a/implementation/04-dev-helpers/composer.lock b/implementation/04-dev-helpers/composer.lock deleted file mode 100644 index 8ac2710..0000000 --- a/implementation/04-dev-helpers/composer.lock +++ /dev/null @@ -1,420 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "e956f9f7a53de7c1c149a093926ab371", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "php-cs-fixer/shim", - "version": "v3.7.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" - }, - "time": "2022-03-07T17:02:59+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.4.10", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.4.10" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-14T10:25:45+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/04-dev-helpers/phpstan.neon b/implementation/04-dev-helpers/phpstan.neon deleted file mode 100644 index c2f33f3..0000000 --- a/implementation/04-dev-helpers/phpstan.neon +++ /dev/null @@ -1,4 +0,0 @@ -parameters: - level: 9 - paths: - - src \ No newline at end of file diff --git a/implementation/04-dev-helpers/public/index.php b/implementation/04-dev-helpers/public/index.php deleted file mode 100644 index 32f5eb3..0000000 --- a/implementation/04-dev-helpers/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -pushHandler(new PrettyPageHandler()); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -throw new Exception("Ooooopsie"); diff --git a/implementation/05-http/.php-cs-fixer.php b/implementation/05-http/.php-cs-fixer.php deleted file mode 100644 index 3db1195..0000000 --- a/implementation/05-http/.php-cs-fixer.php +++ /dev/null @@ -1,41 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'linebreak_after_opening_tag' => true, - 'native_constant_invocation' => true, - 'native_function_invocation' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - 'ordered_imports' => [ - 'sort_algorithm' => 'alpha', - 'imports_order' => [ - 'const', - 'class', - 'function', - ], - ], - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in(__DIR__ . '/src') - ); \ No newline at end of file diff --git a/implementation/05-http/composer.json b/implementation/05-http/composer.json deleted file mode 100644 index a576724..0000000 --- a/implementation/05-http/composer.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "scripts": { - "serve": "php -S localhost:1234 -t ./public", - "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", - "phpstan": "./vendor/bin/phpstan analyze", - "style": "./vendor/bin/php-cs-fixer fix" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "php-cs-fixer/shim": "^3.7", - "symfony/var-dumper": "^6.0" - } -} diff --git a/implementation/05-http/composer.lock b/implementation/05-http/composer.lock deleted file mode 100644 index c8f686e..0000000 --- a/implementation/05-http/composer.lock +++ /dev/null @@ -1,627 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "8f46f82d7ff0a4180389107e37ae5ed0", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "php-cs-fixer/shim", - "version": "v3.7.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" - }, - "time": "2022-03-07T17:02:59+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.4.10", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.4.10" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-14T10:25:45+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/05-http/phpstan-baseline.neon b/implementation/05-http/phpstan-baseline.neon deleted file mode 100644 index e69de29..0000000 diff --git a/implementation/05-http/phpstan.neon b/implementation/05-http/phpstan.neon deleted file mode 100644 index c2f33f3..0000000 --- a/implementation/05-http/phpstan.neon +++ /dev/null @@ -1,4 +0,0 @@ -parameters: - level: 9 - paths: - - src \ No newline at end of file diff --git a/implementation/05-http/public/index.php b/implementation/05-http/public/index.php deleted file mode 100644 index 43d37a2..0000000 --- a/implementation/05-http/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -pushHandler(new PrettyPageHandler()); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$request = ServerRequestFactory::fromGlobals(); -$response = new Response(); -$response->getBody()->write('Hello World!'); - -dd($response); - -/** @var string $name */ -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); diff --git a/implementation/06-router/.php-cs-fixer.php b/implementation/06-router/.php-cs-fixer.php deleted file mode 100644 index 6b8a091..0000000 --- a/implementation/06-router/.php-cs-fixer.php +++ /dev/null @@ -1,44 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'linebreak_after_opening_tag' => true, - 'native_constant_invocation' => true, - 'native_function_invocation' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - 'ordered_imports' => [ - 'sort_algorithm' => 'alpha', - 'imports_order' => [ - 'const', - 'class', - 'function', - ], - ], - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config', - ]) - ); \ No newline at end of file diff --git a/implementation/06-router/composer.json b/implementation/06-router/composer.json deleted file mode 100644 index c046545..0000000 --- a/implementation/06-router/composer.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "scripts": { - "serve": "php -S localhost:1234 -t ./public", - "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", - "phpstan": "./vendor/bin/phpstan analyze", - "style": "./vendor/bin/php-cs-fixer fix" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "php-cs-fixer/shim": "^3.7", - "symfony/var-dumper": "^6.0" - } -} diff --git a/implementation/06-router/composer.lock b/implementation/06-router/composer.lock deleted file mode 100644 index 91a0551..0000000 --- a/implementation/06-router/composer.lock +++ /dev/null @@ -1,677 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "d76b01ed13a5b0b42ce69a745090f7d9", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "php-cs-fixer/shim", - "version": "v3.7.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" - }, - "time": "2022-03-07T17:02:59+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.4.10", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.4.10" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-14T10:25:45+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/06-router/config/routes.php b/implementation/06-router/config/routes.php deleted file mode 100644 index baea1f0..0000000 --- a/implementation/06-router/config/routes.php +++ /dev/null @@ -1,17 +0,0 @@ -addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response())->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new \Laminas\Diactoros\Response())->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}; diff --git a/implementation/06-router/phpstan-baseline.neon b/implementation/06-router/phpstan-baseline.neon deleted file mode 100644 index e69de29..0000000 diff --git a/implementation/06-router/phpstan.neon b/implementation/06-router/phpstan.neon deleted file mode 100644 index c2f33f3..0000000 --- a/implementation/06-router/phpstan.neon +++ /dev/null @@ -1,4 +0,0 @@ -parameters: - level: 9 - paths: - - src \ No newline at end of file diff --git a/implementation/06-router/public/index.php b/implementation/06-router/public/index.php deleted file mode 100644 index 43d37a2..0000000 --- a/implementation/06-router/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -pushHandler(new PrettyPageHandler()); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$request = ServerRequestFactory::fromGlobals(); -$response = new Response(); - -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = simpleDispatcher($routeDefinitionCallback); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -switch ($routeInfo[0]) { - case Dispatcher::METHOD_NOT_ALLOWED: - $response = (new Response())->withStatus(405); - $response->getBody()->write('Method not allowed'); - $response = $response->withStatus(405); - break; - case Dispatcher::FOUND: - $handler = $routeInfo[1]; - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - /** @var ResponseInterface $response */ - $response = call_user_func($handler, $request); - break; - case Dispatcher::NOT_FOUND: - default: - $response = (new Response())->withStatus(404); - $response->getBody()->write('Not Found!'); - break; -} - -/** @var string $name */ -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); diff --git a/implementation/07-dispatching-to-class/.php-cs-fixer.php b/implementation/07-dispatching-to-class/.php-cs-fixer.php deleted file mode 100644 index 6b8a091..0000000 --- a/implementation/07-dispatching-to-class/.php-cs-fixer.php +++ /dev/null @@ -1,44 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'linebreak_after_opening_tag' => true, - 'native_constant_invocation' => true, - 'native_function_invocation' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - 'ordered_imports' => [ - 'sort_algorithm' => 'alpha', - 'imports_order' => [ - 'const', - 'class', - 'function', - ], - ], - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config', - ]) - ); \ No newline at end of file diff --git a/implementation/07-dispatching-to-class/composer.json b/implementation/07-dispatching-to-class/composer.json deleted file mode 100644 index 53f35e7..0000000 --- a/implementation/07-dispatching-to-class/composer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "scripts": { - "serve": "php -S localhost:1234 -t ./public", - "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", - "phpstan": "./vendor/bin/phpstan analyze", - "style": "./vendor/bin/php-cs-fixer fix" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "php-cs-fixer/shim": "^3.7", - "symfony/var-dumper": "^6.0" - } -} diff --git a/implementation/07-dispatching-to-class/composer.lock b/implementation/07-dispatching-to-class/composer.lock deleted file mode 100644 index f2fa011..0000000 --- a/implementation/07-dispatching-to-class/composer.lock +++ /dev/null @@ -1,734 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "b84022e8ec4df1e5ee958e6cbfb2307c", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "php-cs-fixer/shim", - "version": "v3.7.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" - }, - "time": "2022-03-07T17:02:59+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.4.10", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.4.10" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-14T10:25:45+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/07-dispatching-to-class/config/routes.php b/implementation/07-dispatching-to-class/config/routes.php deleted file mode 100644 index af63e42..0000000 --- a/implementation/07-dispatching-to-class/config/routes.php +++ /dev/null @@ -1,8 +0,0 @@ -addRoute('GET', '/hello[/{name}]', [\Lubian\NoFramework\Action\Hello::class, 'handle']); - $r->addRoute('GET', '/another-route', [\Lubian\NoFramework\Action\Another::class, 'handle']); -}; diff --git a/implementation/07-dispatching-to-class/phpstan-baseline.neon b/implementation/07-dispatching-to-class/phpstan-baseline.neon deleted file mode 100644 index e69de29..0000000 diff --git a/implementation/07-dispatching-to-class/phpstan.neon b/implementation/07-dispatching-to-class/phpstan.neon deleted file mode 100644 index c2f33f3..0000000 --- a/implementation/07-dispatching-to-class/phpstan.neon +++ /dev/null @@ -1,4 +0,0 @@ -parameters: - level: 9 - paths: - - src \ No newline at end of file diff --git a/implementation/07-dispatching-to-class/public/index.php b/implementation/07-dispatching-to-class/public/index.php deleted file mode 100644 index 43d37a2..0000000 --- a/implementation/07-dispatching-to-class/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - } -} diff --git a/implementation/07-dispatching-to-class/src/Action/Hello.php b/implementation/07-dispatching-to-class/src/Action/Hello.php deleted file mode 100644 index b516c1a..0000000 --- a/implementation/07-dispatching-to-class/src/Action/Hello.php +++ /dev/null @@ -1,21 +0,0 @@ -getAttribute('name', 'Stranger'); - $response = (new Response())->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - } -} diff --git a/implementation/07-dispatching-to-class/src/Action/InternalServerError.php b/implementation/07-dispatching-to-class/src/Action/InternalServerError.php deleted file mode 100644 index 5f2aa5b..0000000 --- a/implementation/07-dispatching-to-class/src/Action/InternalServerError.php +++ /dev/null @@ -1,20 +0,0 @@ -withStatus(500); - $response->getBody()->write('Internal Server Error.'); - return $response; - } -} diff --git a/implementation/07-dispatching-to-class/src/Action/NotAllowed.php b/implementation/07-dispatching-to-class/src/Action/NotAllowed.php deleted file mode 100644 index 799f4e3..0000000 --- a/implementation/07-dispatching-to-class/src/Action/NotAllowed.php +++ /dev/null @@ -1,20 +0,0 @@ -withStatus(405); - $response->getBody()->write('Method Not Allowed'); - return $response; - } -} diff --git a/implementation/07-dispatching-to-class/src/Action/NotFound.php b/implementation/07-dispatching-to-class/src/Action/NotFound.php deleted file mode 100644 index 0a88dc5..0000000 --- a/implementation/07-dispatching-to-class/src/Action/NotFound.php +++ /dev/null @@ -1,20 +0,0 @@ -withStatus(404); - $response->getBody()->write('Page not found'); - return $response; - } -} diff --git a/implementation/07-dispatching-to-class/src/Bootstrap.php b/implementation/07-dispatching-to-class/src/Bootstrap.php deleted file mode 100644 index c13c6fb..0000000 --- a/implementation/07-dispatching-to-class/src/Bootstrap.php +++ /dev/null @@ -1,102 +0,0 @@ -pushHandler(new PrettyPageHandler()); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$request = ServerRequestFactory::fromGlobals(); -$response = new Response(); - -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = simpleDispatcher($routeDefinitionCallback); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -try { - switch ($routeInfo[0]) { - case Dispatcher::METHOD_NOT_ALLOWED: - throw new NotAllowedException(); - case Dispatcher::FOUND: - /** @var RequestHandlerInterface $handler */ - $handler = new $routeInfo[1][0](); - $method = $routeInfo[1][1]; - if (!$handler instanceof RequestHandlerInterface) { - throw new InternalServerErrorException(); - } - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->$method($request); - break; - case Dispatcher::NOT_FOUND: - default: - throw new NotFoundException(); - } -} catch (NotAllowedException) { - $response = (new NotAllowed())->handle($request); -} catch (NotFoundException) { - $response = (new NotFound())->handle($request); -} catch (Exception) { - $response = (new InternalServerError())->handle($request); -} - -/** @var string $name */ -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); diff --git a/implementation/07-dispatching-to-class/src/Exception/InternalServerErrorException.php b/implementation/07-dispatching-to-class/src/Exception/InternalServerErrorException.php deleted file mode 100644 index aa3fddd..0000000 --- a/implementation/07-dispatching-to-class/src/Exception/InternalServerErrorException.php +++ /dev/null @@ -1,11 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'linebreak_after_opening_tag' => true, - 'native_constant_invocation' => true, - 'native_function_invocation' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - 'ordered_imports' => [ - 'sort_algorithm' => 'alpha', - 'imports_order' => [ - 'const', - 'class', - 'function', - ], - ], - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config', - ]) - ); \ No newline at end of file diff --git a/implementation/08-inversion-of-control/composer.json b/implementation/08-inversion-of-control/composer.json deleted file mode 100644 index 53f35e7..0000000 --- a/implementation/08-inversion-of-control/composer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "scripts": { - "serve": "php -S localhost:1234 -t ./public", - "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", - "phpstan": "./vendor/bin/phpstan analyze", - "style": "./vendor/bin/php-cs-fixer fix" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "php-cs-fixer/shim": "^3.7", - "symfony/var-dumper": "^6.0" - } -} diff --git a/implementation/08-inversion-of-control/composer.lock b/implementation/08-inversion-of-control/composer.lock deleted file mode 100644 index f2fa011..0000000 --- a/implementation/08-inversion-of-control/composer.lock +++ /dev/null @@ -1,734 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "b84022e8ec4df1e5ee958e6cbfb2307c", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "php-cs-fixer/shim", - "version": "v3.7.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" - }, - "time": "2022-03-07T17:02:59+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.4.10", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.4.10" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-14T10:25:45+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/08-inversion-of-control/config/routes.php b/implementation/08-inversion-of-control/config/routes.php deleted file mode 100644 index af63e42..0000000 --- a/implementation/08-inversion-of-control/config/routes.php +++ /dev/null @@ -1,8 +0,0 @@ -addRoute('GET', '/hello[/{name}]', [\Lubian\NoFramework\Action\Hello::class, 'handle']); - $r->addRoute('GET', '/another-route', [\Lubian\NoFramework\Action\Another::class, 'handle']); -}; diff --git a/implementation/08-inversion-of-control/phpstan-baseline.neon b/implementation/08-inversion-of-control/phpstan-baseline.neon deleted file mode 100644 index e69de29..0000000 diff --git a/implementation/08-inversion-of-control/phpstan.neon b/implementation/08-inversion-of-control/phpstan.neon deleted file mode 100644 index c2f33f3..0000000 --- a/implementation/08-inversion-of-control/phpstan.neon +++ /dev/null @@ -1,4 +0,0 @@ -parameters: - level: 9 - paths: - - src \ No newline at end of file diff --git a/implementation/08-inversion-of-control/public/index.php b/implementation/08-inversion-of-control/public/index.php deleted file mode 100644 index 43d37a2..0000000 --- a/implementation/08-inversion-of-control/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -response->getBody()->write('This works too!'); - return $this->response; - } -} diff --git a/implementation/08-inversion-of-control/src/Action/Hello.php b/implementation/08-inversion-of-control/src/Action/Hello.php deleted file mode 100644 index b45021e..0000000 --- a/implementation/08-inversion-of-control/src/Action/Hello.php +++ /dev/null @@ -1,20 +0,0 @@ -getAttribute('name', 'Stranger'); - $this->response->getBody()->write('Hello ' . $name . '!'); - return $this->response; - } -} diff --git a/implementation/08-inversion-of-control/src/Action/InternalServerError.php b/implementation/08-inversion-of-control/src/Action/InternalServerError.php deleted file mode 100644 index d1acbb8..0000000 --- a/implementation/08-inversion-of-control/src/Action/InternalServerError.php +++ /dev/null @@ -1,19 +0,0 @@ -response->getBody()->write('Internal Server Error.'); - return $this->response->withStatus(500); - } -} diff --git a/implementation/08-inversion-of-control/src/Action/NotAllowed.php b/implementation/08-inversion-of-control/src/Action/NotAllowed.php deleted file mode 100644 index 83abb60..0000000 --- a/implementation/08-inversion-of-control/src/Action/NotAllowed.php +++ /dev/null @@ -1,19 +0,0 @@ -response->getBody()->write('Method Not Allowed'); - return $this->response->withStatus(405); - } -} diff --git a/implementation/08-inversion-of-control/src/Action/NotFound.php b/implementation/08-inversion-of-control/src/Action/NotFound.php deleted file mode 100644 index ceee42f..0000000 --- a/implementation/08-inversion-of-control/src/Action/NotFound.php +++ /dev/null @@ -1,19 +0,0 @@ -response->getBody()->write('Page not found'); - return $this->response->withStatus(404); - } -} diff --git a/implementation/08-inversion-of-control/src/Bootstrap.php b/implementation/08-inversion-of-control/src/Bootstrap.php deleted file mode 100644 index c7ec6e8..0000000 --- a/implementation/08-inversion-of-control/src/Bootstrap.php +++ /dev/null @@ -1,102 +0,0 @@ -pushHandler(new PrettyPageHandler()); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$request = ServerRequestFactory::fromGlobals(); -$response = new Response(); - -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = simpleDispatcher($routeDefinitionCallback); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -try { - switch ($routeInfo[0]) { - case Dispatcher::METHOD_NOT_ALLOWED: - throw new NotAllowedException(); - case Dispatcher::FOUND: - /** @var RequestHandlerInterface $handler */ - $handler = new $routeInfo[1][0]($response); - $method = $routeInfo[1][1]; - if (!$handler instanceof RequestHandlerInterface) { - throw new InternalServerErrorException(); - } - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->$method($request); - break; - case Dispatcher::NOT_FOUND: - default: - throw new NotFoundException(); - } -} catch (NotAllowedException) { - $response = (new NotAllowed($response))->handle($request); -} catch (NotFoundException) { - $response = (new NotFound($response))->handle($request); -} catch (Exception) { - $response = (new InternalServerError($response))->handle($request); -} - -/** @var string $name */ -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); diff --git a/implementation/08-inversion-of-control/src/Exception/InternalServerErrorException.php b/implementation/08-inversion-of-control/src/Exception/InternalServerErrorException.php deleted file mode 100644 index aa3fddd..0000000 --- a/implementation/08-inversion-of-control/src/Exception/InternalServerErrorException.php +++ /dev/null @@ -1,11 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'linebreak_after_opening_tag' => true, - 'native_constant_invocation' => true, - 'native_function_invocation' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - 'ordered_imports' => [ - 'sort_algorithm' => 'alpha', - 'imports_order' => [ - 'const', - 'class', - 'function', - ], - ], - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config', - ]) - ); \ No newline at end of file diff --git a/implementation/09-dependency-injector/composer.json b/implementation/09-dependency-injector/composer.json deleted file mode 100644 index a38d437..0000000 --- a/implementation/09-dependency-injector/composer.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "scripts": { - "serve": "php -S localhost:1234 -t ./public", - "prodserve": "ENVIRONMENT=prod php -S localhost:1234 -t ./public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "style": "./vendor/bin/php-cs-fixer fix" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "php-cs-fixer/shim": "^3.7", - "symfony/var-dumper": "^6.0" - } -} diff --git a/implementation/09-dependency-injector/composer.lock b/implementation/09-dependency-injector/composer.lock deleted file mode 100644 index cb0aa21..0000000 --- a/implementation/09-dependency-injector/composer.lock +++ /dev/null @@ -1,1020 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "176033a9d3cd7179cb7bb9608fa24210", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "php-cs-fixer/shim", - "version": "v3.7.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "reference": "8c55e49cf26c9103ed005c8b2005d12ccc57814b", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.7.0" - }, - "time": "2022-03-07T17:02:59+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.4.10", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.4.10" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-14T10:25:45+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/09-dependency-injector/config/dependencies.php b/implementation/09-dependency-injector/config/dependencies.php deleted file mode 100644 index ac1d962..0000000 --- a/implementation/09-dependency-injector/config/dependencies.php +++ /dev/null @@ -1,11 +0,0 @@ -addDefinitions([ - \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), - \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), -]); - -return $builder->build(); diff --git a/implementation/09-dependency-injector/config/routes.php b/implementation/09-dependency-injector/config/routes.php deleted file mode 100644 index af63e42..0000000 --- a/implementation/09-dependency-injector/config/routes.php +++ /dev/null @@ -1,8 +0,0 @@ -addRoute('GET', '/hello[/{name}]', [\Lubian\NoFramework\Action\Hello::class, 'handle']); - $r->addRoute('GET', '/another-route', [\Lubian\NoFramework\Action\Another::class, 'handle']); -}; diff --git a/implementation/09-dependency-injector/phpstan-baseline.neon b/implementation/09-dependency-injector/phpstan-baseline.neon deleted file mode 100644 index fe0b762..0000000 --- a/implementation/09-dependency-injector/phpstan-baseline.neon +++ /dev/null @@ -1,7 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Cannot call method handle\\(\\) on mixed\\.$#" - count: 3 - path: src/Bootstrap.php - diff --git a/implementation/09-dependency-injector/phpstan.neon b/implementation/09-dependency-injector/phpstan.neon deleted file mode 100644 index 28914db..0000000 --- a/implementation/09-dependency-injector/phpstan.neon +++ /dev/null @@ -1,7 +0,0 @@ -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src \ No newline at end of file diff --git a/implementation/09-dependency-injector/public/index.php b/implementation/09-dependency-injector/public/index.php deleted file mode 100644 index 43d37a2..0000000 --- a/implementation/09-dependency-injector/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -response->getBody()->write('This works too!'); - return $this->response; - } -} diff --git a/implementation/09-dependency-injector/src/Action/Hello.php b/implementation/09-dependency-injector/src/Action/Hello.php deleted file mode 100644 index b45021e..0000000 --- a/implementation/09-dependency-injector/src/Action/Hello.php +++ /dev/null @@ -1,20 +0,0 @@ -getAttribute('name', 'Stranger'); - $this->response->getBody()->write('Hello ' . $name . '!'); - return $this->response; - } -} diff --git a/implementation/09-dependency-injector/src/Action/InternalServerError.php b/implementation/09-dependency-injector/src/Action/InternalServerError.php deleted file mode 100644 index d1acbb8..0000000 --- a/implementation/09-dependency-injector/src/Action/InternalServerError.php +++ /dev/null @@ -1,19 +0,0 @@ -response->getBody()->write('Internal Server Error.'); - return $this->response->withStatus(500); - } -} diff --git a/implementation/09-dependency-injector/src/Action/NotAllowed.php b/implementation/09-dependency-injector/src/Action/NotAllowed.php deleted file mode 100644 index 83abb60..0000000 --- a/implementation/09-dependency-injector/src/Action/NotAllowed.php +++ /dev/null @@ -1,19 +0,0 @@ -response->getBody()->write('Method Not Allowed'); - return $this->response->withStatus(405); - } -} diff --git a/implementation/09-dependency-injector/src/Action/NotFound.php b/implementation/09-dependency-injector/src/Action/NotFound.php deleted file mode 100644 index ceee42f..0000000 --- a/implementation/09-dependency-injector/src/Action/NotFound.php +++ /dev/null @@ -1,19 +0,0 @@ -response->getBody()->write('Page not found'); - return $this->response->withStatus(404); - } -} diff --git a/implementation/09-dependency-injector/src/Bootstrap.php b/implementation/09-dependency-injector/src/Bootstrap.php deleted file mode 100644 index a059b66..0000000 --- a/implementation/09-dependency-injector/src/Bootstrap.php +++ /dev/null @@ -1,106 +0,0 @@ -pushHandler(new PrettyPageHandler()); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -/** @var ContainerInterface $container */ -$container = require __DIR__ . '/../config/dependencies.php'; -/** @var ServerRequestInterface $request */ -$request = $container->get(ServerRequestInterface::class); - -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = simpleDispatcher($routeDefinitionCallback); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -try { - switch ($routeInfo[0]) { - case Dispatcher::METHOD_NOT_ALLOWED: - throw new NotAllowedException(); - case Dispatcher::FOUND: - /** @var RequestHandlerInterface $handler */ - $handler = $container->get($routeInfo[1][0]); - $method = $routeInfo[1][1]; - if (!$handler instanceof RequestHandlerInterface) { - throw new InternalServerErrorException(); - } - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->$method($request); - break; - case Dispatcher::NOT_FOUND: - default: - throw new NotFoundException(); - } -} catch (NotAllowedException) { - $response = $container->get(NotAllowed::class)->handle($request); -} catch (NotFoundException) { - $response = $container->get(NotFound::class)->handle($request); -} catch (Exception) { - $response = $container->get(InternalServerError::class)->handle($request); -} - -/** @var string $name */ -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); diff --git a/implementation/09-dependency-injector/src/Exception/InternalServerErrorException.php b/implementation/09-dependency-injector/src/Exception/InternalServerErrorException.php deleted file mode 100644 index aa3fddd..0000000 --- a/implementation/09-dependency-injector/src/Exception/InternalServerErrorException.php +++ /dev/null @@ -1,11 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'linebreak_after_opening_tag' => true, - 'native_constant_invocation' => true, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - 'ordered_imports' => [ - 'sort_algorithm' => 'alpha', - 'imports_order' => [ - 'const', - 'class', - 'function', - ], - ], - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config', - ]) - ); \ No newline at end of file diff --git a/implementation/10-invoker/composer.json b/implementation/10-invoker/composer.json deleted file mode 100644 index 3c17ae5..0000000 --- a/implementation/10-invoker/composer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "lubian/no-framework", - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0" - }, - "scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "style": "./vendor/bin/php-cs-fixer fix" - } -} diff --git a/implementation/10-invoker/composer.lock b/implementation/10-invoker/composer.lock deleted file mode 100644 index d300ec9..0000000 --- a/implementation/10-invoker/composer.lock +++ /dev/null @@ -1,1020 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "c8641669dc93f9bfa8f95f20a8598451", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.4.10", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", - "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.4.10" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-14T10:25:45+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/10-invoker/config/dependencies.php b/implementation/10-invoker/config/dependencies.php deleted file mode 100644 index 3708e0d..0000000 --- a/implementation/10-invoker/config/dependencies.php +++ /dev/null @@ -1,12 +0,0 @@ -addDefinitions([ - \Psr\Http\Message\ResponseInterface::class => fn () => new \Laminas\Diactoros\Response(), - \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), - \Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), -]); - -return $builder->build(); diff --git a/implementation/10-invoker/config/routes.php b/implementation/10-invoker/config/routes.php deleted file mode 100644 index a60931c..0000000 --- a/implementation/10-invoker/config/routes.php +++ /dev/null @@ -1,18 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); - $r->addRoute( - 'GET', - '/', - fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello') - ); -}; diff --git a/implementation/10-invoker/phpstan.neon b/implementation/10-invoker/phpstan.neon deleted file mode 100644 index c6ce9bd..0000000 --- a/implementation/10-invoker/phpstan.neon +++ /dev/null @@ -1,5 +0,0 @@ -parameters: - level: 9 - paths: - - src - - config \ No newline at end of file diff --git a/implementation/10-invoker/public/favicon.ico b/implementation/10-invoker/public/favicon.ico deleted file mode 100644 index 09499b8b3b3201e0f50088e3ac42e167778d1153..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/10-invoker/public/index.php b/implementation/10-invoker/public/index.php deleted file mode 100644 index db23804..0000000 --- a/implementation/10-invoker/public/index.php +++ /dev/null @@ -1,6 +0,0 @@ -withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - $nowString = $now->get()->format('H:i:s'); - $response->getBody()->write(' The Time is ' . $nowString); - return $response; - } -} diff --git a/implementation/10-invoker/src/Action/Other.php b/implementation/10-invoker/src/Action/Other.php deleted file mode 100644 index 3563983..0000000 --- a/implementation/10-invoker/src/Action/Other.php +++ /dev/null @@ -1,20 +0,0 @@ -response->withStatus(200); - $response->getBody()->write('This works too'); - return $response; - } -} diff --git a/implementation/10-invoker/src/Bootstrap.php b/implementation/10-invoker/src/Bootstrap.php deleted file mode 100644 index d3bfc96..0000000 --- a/implementation/10-invoker/src/Bootstrap.php +++ /dev/null @@ -1,110 +0,0 @@ -pushHandler(new PrettyPageHandler()); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log("ERROR: " . $e->getMessage(), $e->getCode()); - echo 'AN ERROR HAPPENED!!!'; - }); -} - -$whoops->register(); - -/** @var ContainerInterface $container */ -$container = require __DIR__ . '/../config/dependencies.php'; - -/** @var InvokerInterface $invoker */ -$invoker = $container->get(InvokerInterface::class); - -/** @var ServerRequestInterface $request */ -$request = $container->get(ServerRequestInterface::class); - -/** @var ResponseInterface $response */ -$response = $container->get(ResponseInterface::class); - -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = simpleDispatcher($routeDefinitionCallback); - -$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri()->getPath()); - -try { - switch ($routeInfo[0]) { - case Dispatcher::FOUND: - $handler = $routeInfo[1]; - $args = $routeInfo[2]; - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $args['request'] = $request; - $response = $container->call($handler, $args); - break; - case Dispatcher::METHOD_NOT_ALLOWED: - throw new MethodNotAllowed(); - case Dispatcher::NOT_FOUND: - default: - throw new NotFound(); - } -} catch (NotFound) { - $response = $response->withStatus(404); - $response->getBody()->write('Not Found'); -} catch (MethodNotAllowed) { - $response = $response->withStatus(405); - $response->getBody()->write('Method not Allowed'); -} catch (Exception $e) { - throw new InternalServerError($e->getMessage(), $e->getCode(), $e); -} - - - -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase(), -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); diff --git a/implementation/10-invoker/src/Exception/InternalServerError.php b/implementation/10-invoker/src/Exception/InternalServerError.php deleted file mode 100644 index a7b627b..0000000 --- a/implementation/10-invoker/src/Exception/InternalServerError.php +++ /dev/null @@ -1,11 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config' - ]) - ); \ No newline at end of file diff --git a/implementation/11-templating/.phpcs.xml.dist b/implementation/11-templating/.phpcs.xml.dist deleted file mode 100644 index 3b433f6..0000000 --- a/implementation/11-templating/.phpcs.xml.dist +++ /dev/null @@ -1,9 +0,0 @@ - - - - - src - config - - - \ No newline at end of file diff --git a/implementation/11-templating/composer.json b/implementation/11-templating/composer.json deleted file mode 100644 index 2c61bdd..0000000 --- a/implementation/11-templating/composer.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3", - "mustache/mustache": "^2.14" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require-dev": { - "phpstan/phpstan": "^1.5", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0", - "squizlabs/php_codesniffer": "^3.6", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1", - "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0" - }, - "config": { - "allow-plugins": { - "phpstan/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" - } -} diff --git a/implementation/11-templating/composer.lock b/implementation/11-templating/composer.lock deleted file mode 100644 index 37ee56e..0000000 --- a/implementation/11-templating/composer.lock +++ /dev/null @@ -1,1550 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "e237b9a5b4210f235b1609a7ce3e065a", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "doctrine/coding-standard", - "version": "8.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/coding-standard.git", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "slevomat/coding-standard": "^6.4.1", - "squizlabs/php_codesniffer": "^3.5.8" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Steve Müller", - "email": "st.mueller@dzh-online.de" - } - ], - "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", - "keywords": [ - "checks", - "code", - "coding", - "cs", - "doctrine", - "rules", - "sniffer", - "sniffs", - "standard", - "style" - ], - "support": { - "issues": "https://github.com/doctrine/coding-standard/issues", - "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" - }, - "time": "2021-04-03T10:54:55+00:00" - }, - { - "name": "mnapoli/hard-mode", - "version": "0.3.0", - "source": { - "type": "git", - "url": "https://github.com/mnapoli/hard-mode.git", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", - "shasum": "" - }, - "require": { - "doctrine/coding-standard": "^8.0" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Hard mode for PHP", - "support": { - "issues": "https://github.com/mnapoli/hard-mode/issues", - "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" - }, - "time": "2020-10-12T07:54:37+00:00" - }, - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.9", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.26", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/master" - }, - "time": "2020-08-03T20:32:43+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.0" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-24T18:18:00+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^1.2.0" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" - }, - "time": "2021-11-18T09:30:29+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "6.4.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpstan/phpstan": "0.12.48", - "phpstan/phpstan-deprecation-rules": "0.12.5", - "phpstan/phpstan-phpunit": "0.12.16", - "phpstan/phpstan-strict-rules": "0.12.5", - "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2020-10-05T12:39:37+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/11-templating/config/dependencies.php b/implementation/11-templating/config/dependencies.php deleted file mode 100644 index c3e65a8..0000000 --- a/implementation/11-templating/config/dependencies.php +++ /dev/null @@ -1,32 +0,0 @@ -addDefinitions([ - Settings::class => fn () => require __DIR__ . '/settings.php', - ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - Mustache_Loader_FilesystemLoader::class => function (Settings $s) { - return new Mustache_Loader_FilesystemLoader( - $s->templateDir, - [ - 'extension' => $s->templateExtension, - ], - ); - }, - Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $mfl) => new Mustache_Engine(['loader' => $mfl]), -]); - -return $builder->build(); diff --git a/implementation/11-templating/config/routes.php b/implementation/11-templating/config/routes.php deleted file mode 100644 index 1bc00bc..0000000 --- a/implementation/11-templating/config/routes.php +++ /dev/null @@ -1,12 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); - $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -}; diff --git a/implementation/11-templating/config/settings.php b/implementation/11-templating/config/settings.php deleted file mode 100644 index ad2ea0c..0000000 --- a/implementation/11-templating/config/settings.php +++ /dev/null @@ -1,9 +0,0 @@ -aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/11-templating/public/index.php b/implementation/11-templating/public/index.php deleted file mode 100644 index d93da3a..0000000 --- a/implementation/11-templating/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render('hello', $data); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/11-templating/src/Action/Other.php b/implementation/11-templating/src/Action/Other.php deleted file mode 100644 index 895796e..0000000 --- a/implementation/11-templating/src/Action/Other.php +++ /dev/null @@ -1,19 +0,0 @@ -getBody(); - - $body->write('This works too!'); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/11-templating/src/Bootstrap.php b/implementation/11-templating/src/Bootstrap.php deleted file mode 100644 index 2a45e08..0000000 --- a/implementation/11-templating/src/Bootstrap.php +++ /dev/null @@ -1,110 +0,0 @@ -pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$container = require __DIR__ . '/../config/dependencies.php'; -assert($container instanceof ContainerInterface); - -$request = $container->get(ServerRequestInterface::class); -assert($request instanceof ServerRequestInterface); - -$responseFactory = $container->get(ResponseFactory::class); -assert($responseFactory instanceof ResponseFactory); - -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = simpleDispatcher($routeDefinitionCallback); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -try { - switch ($routeInfo[0]) { - case Dispatcher::FOUND: - $handler = $routeInfo[1]; - $vars = $routeInfo[2] ?? []; - foreach ($vars as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $vars['request'] = $request; - $invoker = $container->get(InvokerInterface::class); - assert($invoker instanceof InvokerInterface); - $response = $invoker->call($handler, $vars); - assert($response instanceof ResponseInterface); - break; - case Dispatcher::METHOD_NOT_ALLOWED: - throw new MethodNotAllowed; - - case Dispatcher::NOT_FOUND: - default: - throw new NotFound; - } -} catch (MethodNotAllowed) { - $response = $responseFactory->createResponse(405); -} catch (NotFound) { - $response = $responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); -} catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); -} - -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); diff --git a/implementation/11-templating/src/Exception/InternalServerError.php b/implementation/11-templating/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/implementation/11-templating/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ -engine->render($template, $data); - } -} diff --git a/implementation/11-templating/src/Template/Renderer.php b/implementation/11-templating/src/Template/Renderer.php deleted file mode 100644 index ff916ed..0000000 --- a/implementation/11-templating/src/Template/Renderer.php +++ /dev/null @@ -1,11 +0,0 @@ - $data - */ - public function render(string $template, array $data = []): string; -} diff --git a/implementation/11-templating/templates/hello.html b/implementation/11-templating/templates/hello.html deleted file mode 100644 index 0e21f2a..0000000 --- a/implementation/11-templating/templates/hello.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Hello World - - -

Hello {{name}}

-

The time is {{now}}

- - \ No newline at end of file diff --git a/implementation/12-configuration/.php-cs-fixer.php b/implementation/12-configuration/.php-cs-fixer.php deleted file mode 100644 index 705a7d7..0000000 --- a/implementation/12-configuration/.php-cs-fixer.php +++ /dev/null @@ -1,38 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config' - ]) - ); \ No newline at end of file diff --git a/implementation/12-configuration/.phpcs.xml.dist b/implementation/12-configuration/.phpcs.xml.dist deleted file mode 100644 index 3b433f6..0000000 --- a/implementation/12-configuration/.phpcs.xml.dist +++ /dev/null @@ -1,9 +0,0 @@ - - - - - src - config - - - \ No newline at end of file diff --git a/implementation/12-configuration/composer.json b/implementation/12-configuration/composer.json deleted file mode 100644 index 2c61bdd..0000000 --- a/implementation/12-configuration/composer.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3", - "mustache/mustache": "^2.14" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require-dev": { - "phpstan/phpstan": "^1.5", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0", - "squizlabs/php_codesniffer": "^3.6", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1", - "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0" - }, - "config": { - "allow-plugins": { - "phpstan/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" - } -} diff --git a/implementation/12-configuration/composer.lock b/implementation/12-configuration/composer.lock deleted file mode 100644 index 37ee56e..0000000 --- a/implementation/12-configuration/composer.lock +++ /dev/null @@ -1,1550 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "e237b9a5b4210f235b1609a7ce3e065a", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "doctrine/coding-standard", - "version": "8.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/coding-standard.git", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "slevomat/coding-standard": "^6.4.1", - "squizlabs/php_codesniffer": "^3.5.8" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Steve Müller", - "email": "st.mueller@dzh-online.de" - } - ], - "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", - "keywords": [ - "checks", - "code", - "coding", - "cs", - "doctrine", - "rules", - "sniffer", - "sniffs", - "standard", - "style" - ], - "support": { - "issues": "https://github.com/doctrine/coding-standard/issues", - "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" - }, - "time": "2021-04-03T10:54:55+00:00" - }, - { - "name": "mnapoli/hard-mode", - "version": "0.3.0", - "source": { - "type": "git", - "url": "https://github.com/mnapoli/hard-mode.git", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", - "shasum": "" - }, - "require": { - "doctrine/coding-standard": "^8.0" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Hard mode for PHP", - "support": { - "issues": "https://github.com/mnapoli/hard-mode/issues", - "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" - }, - "time": "2020-10-12T07:54:37+00:00" - }, - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.9", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.26", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/master" - }, - "time": "2020-08-03T20:32:43+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.0" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-24T18:18:00+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^1.2.0" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" - }, - "time": "2021-11-18T09:30:29+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "6.4.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpstan/phpstan": "0.12.48", - "phpstan/phpstan-deprecation-rules": "0.12.5", - "phpstan/phpstan-phpunit": "0.12.16", - "phpstan/phpstan-strict-rules": "0.12.5", - "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2020-10-05T12:39:37+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/12-configuration/config/dependencies.php b/implementation/12-configuration/config/dependencies.php deleted file mode 100644 index 2957edb..0000000 --- a/implementation/12-configuration/config/dependencies.php +++ /dev/null @@ -1,22 +0,0 @@ - fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), -]; diff --git a/implementation/12-configuration/config/routes.php b/implementation/12-configuration/config/routes.php deleted file mode 100644 index 1bc00bc..0000000 --- a/implementation/12-configuration/config/routes.php +++ /dev/null @@ -1,12 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); - $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -}; diff --git a/implementation/12-configuration/config/settings.php b/implementation/12-configuration/config/settings.php deleted file mode 100644 index 0dc42b6..0000000 --- a/implementation/12-configuration/config/settings.php +++ /dev/null @@ -1,10 +0,0 @@ -aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/12-configuration/public/index.php b/implementation/12-configuration/public/index.php deleted file mode 100644 index d93da3a..0000000 --- a/implementation/12-configuration/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render('hello', $data); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/12-configuration/src/Action/Other.php b/implementation/12-configuration/src/Action/Other.php deleted file mode 100644 index 895796e..0000000 --- a/implementation/12-configuration/src/Action/Other.php +++ /dev/null @@ -1,19 +0,0 @@ -getBody(); - - $body->write('This works too!'); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/12-configuration/src/Bootstrap.php b/implementation/12-configuration/src/Bootstrap.php deleted file mode 100644 index f47341e..0000000 --- a/implementation/12-configuration/src/Bootstrap.php +++ /dev/null @@ -1,111 +0,0 @@ -getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - - -$request = $container->get(ServerRequestInterface::class); -assert($request instanceof ServerRequestInterface); - -$responseFactory = $container->get(ResponseFactory::class); -assert($responseFactory instanceof ResponseFactory); - -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = simpleDispatcher($routeDefinitionCallback); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -try { - switch ($routeInfo[0]) { - case Dispatcher::FOUND: - $handler = $routeInfo[1]; - $vars = $routeInfo[2] ?? []; - foreach ($vars as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $vars['request'] = $request; - $invoker = $container->get(InvokerInterface::class); - assert($invoker instanceof InvokerInterface); - $response = $invoker->call($handler, $vars); - assert($response instanceof ResponseInterface); - break; - case Dispatcher::METHOD_NOT_ALLOWED: - throw new MethodNotAllowed; - - case Dispatcher::NOT_FOUND: - default: - throw new NotFound; - } -} catch (MethodNotAllowed) { - $response = $responseFactory->createResponse(405); -} catch (NotFound) { - $response = $responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); -} catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); -} - -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); diff --git a/implementation/12-configuration/src/Exception/InternalServerError.php b/implementation/12-configuration/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/implementation/12-configuration/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ -filePath; - } -} diff --git a/implementation/12-configuration/src/Factory/SettingsContainerProvider.php b/implementation/12-configuration/src/Factory/SettingsContainerProvider.php deleted file mode 100644 index 20609bf..0000000 --- a/implementation/12-configuration/src/Factory/SettingsContainerProvider.php +++ /dev/null @@ -1,25 +0,0 @@ -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn () => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} diff --git a/implementation/12-configuration/src/Factory/SettingsProvider.php b/implementation/12-configuration/src/Factory/SettingsProvider.php deleted file mode 100644 index ce1c5f0..0000000 --- a/implementation/12-configuration/src/Factory/SettingsProvider.php +++ /dev/null @@ -1,10 +0,0 @@ -engine->render($template, $data); - } -} diff --git a/implementation/12-configuration/src/Template/Renderer.php b/implementation/12-configuration/src/Template/Renderer.php deleted file mode 100644 index ff916ed..0000000 --- a/implementation/12-configuration/src/Template/Renderer.php +++ /dev/null @@ -1,11 +0,0 @@ - $data - */ - public function render(string $template, array $data = []): string; -} diff --git a/implementation/12-configuration/templates/hello.html b/implementation/12-configuration/templates/hello.html deleted file mode 100644 index 0e21f2a..0000000 --- a/implementation/12-configuration/templates/hello.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Hello World - - -

Hello {{name}}

-

The time is {{now}}

- - \ No newline at end of file diff --git a/implementation/13-refactoring/.php-cs-fixer.php b/implementation/13-refactoring/.php-cs-fixer.php deleted file mode 100644 index 705a7d7..0000000 --- a/implementation/13-refactoring/.php-cs-fixer.php +++ /dev/null @@ -1,38 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config' - ]) - ); \ No newline at end of file diff --git a/implementation/13-refactoring/.phpcs.xml.dist b/implementation/13-refactoring/.phpcs.xml.dist deleted file mode 100644 index 3b433f6..0000000 --- a/implementation/13-refactoring/.phpcs.xml.dist +++ /dev/null @@ -1,9 +0,0 @@ - - - - - src - config - - - \ No newline at end of file diff --git a/implementation/13-refactoring/composer.json b/implementation/13-refactoring/composer.json deleted file mode 100644 index 92e7538..0000000 --- a/implementation/13-refactoring/composer.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3", - "mustache/mustache": "^2.14", - "psr/http-server-middleware": "^1.0" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require-dev": { - "phpstan/phpstan": "^1.5", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0", - "squizlabs/php_codesniffer": "^3.6", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1", - "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0" - }, - "config": { - "allow-plugins": { - "phpstan/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" - } -} diff --git a/implementation/13-refactoring/composer.lock b/implementation/13-refactoring/composer.lock deleted file mode 100644 index c3bb867..0000000 --- a/implementation/13-refactoring/composer.lock +++ /dev/null @@ -1,1607 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "eb13263f65ee72240c5f25ab82caddd0", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/http-server-middleware", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-middleware.git", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0", - "psr/http-server-handler": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side middleware", - "keywords": [ - "http", - "http-interop", - "middleware", - "psr", - "psr-15", - "psr-7", - "request", - "response" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-middleware/issues", - "source": "https://github.com/php-fig/http-server-middleware/tree/master" - }, - "time": "2018-10-30T17:12:04+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "doctrine/coding-standard", - "version": "8.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/coding-standard.git", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "slevomat/coding-standard": "^6.4.1", - "squizlabs/php_codesniffer": "^3.5.8" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Steve Müller", - "email": "st.mueller@dzh-online.de" - } - ], - "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", - "keywords": [ - "checks", - "code", - "coding", - "cs", - "doctrine", - "rules", - "sniffer", - "sniffs", - "standard", - "style" - ], - "support": { - "issues": "https://github.com/doctrine/coding-standard/issues", - "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" - }, - "time": "2021-04-03T10:54:55+00:00" - }, - { - "name": "mnapoli/hard-mode", - "version": "0.3.0", - "source": { - "type": "git", - "url": "https://github.com/mnapoli/hard-mode.git", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", - "shasum": "" - }, - "require": { - "doctrine/coding-standard": "^8.0" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Hard mode for PHP", - "support": { - "issues": "https://github.com/mnapoli/hard-mode/issues", - "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" - }, - "time": "2020-10-12T07:54:37+00:00" - }, - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.9", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.26", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/master" - }, - "time": "2020-08-03T20:32:43+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.0" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-24T18:18:00+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^1.2.0" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" - }, - "time": "2021-11-18T09:30:29+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "6.4.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpstan/phpstan": "0.12.48", - "phpstan/phpstan-deprecation-rules": "0.12.5", - "phpstan/phpstan-phpunit": "0.12.16", - "phpstan/phpstan-strict-rules": "0.12.5", - "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2020-10-05T12:39:37+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/13-refactoring/config/dependencies.php b/implementation/13-refactoring/config/dependencies.php deleted file mode 100644 index c26cbe3..0000000 --- a/implementation/13-refactoring/config/dependencies.php +++ /dev/null @@ -1,39 +0,0 @@ - fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, - Emitter::class => fn (BasicEmitter $e) => $e, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), - RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, -]; diff --git a/implementation/13-refactoring/config/routes.php b/implementation/13-refactoring/config/routes.php deleted file mode 100644 index 1bc00bc..0000000 --- a/implementation/13-refactoring/config/routes.php +++ /dev/null @@ -1,12 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); - $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -}; diff --git a/implementation/13-refactoring/config/settings.php b/implementation/13-refactoring/config/settings.php deleted file mode 100644 index 0dc42b6..0000000 --- a/implementation/13-refactoring/config/settings.php +++ /dev/null @@ -1,10 +0,0 @@ -aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/13-refactoring/public/index.php b/implementation/13-refactoring/public/index.php deleted file mode 100644 index d93da3a..0000000 --- a/implementation/13-refactoring/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render('hello', $data); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/13-refactoring/src/Action/Other.php b/implementation/13-refactoring/src/Action/Other.php deleted file mode 100644 index 895796e..0000000 --- a/implementation/13-refactoring/src/Action/Other.php +++ /dev/null @@ -1,19 +0,0 @@ -getBody(); - - $body->write('This works too!'); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/13-refactoring/src/Bootstrap.php b/implementation/13-refactoring/src/Bootstrap.php deleted file mode 100644 index 2a3cc85..0000000 --- a/implementation/13-refactoring/src/Bootstrap.php +++ /dev/null @@ -1,40 +0,0 @@ -getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); diff --git a/implementation/13-refactoring/src/Exception/InternalServerError.php b/implementation/13-refactoring/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/implementation/13-refactoring/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ -factory::fromGlobals(); - } - - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} diff --git a/implementation/13-refactoring/src/Factory/FileSystemSettingsProvider.php b/implementation/13-refactoring/src/Factory/FileSystemSettingsProvider.php deleted file mode 100644 index 3a847ec..0000000 --- a/implementation/13-refactoring/src/Factory/FileSystemSettingsProvider.php +++ /dev/null @@ -1,18 +0,0 @@ -filePath; - } -} diff --git a/implementation/13-refactoring/src/Factory/RequestFactory.php b/implementation/13-refactoring/src/Factory/RequestFactory.php deleted file mode 100644 index 2b17abc..0000000 --- a/implementation/13-refactoring/src/Factory/RequestFactory.php +++ /dev/null @@ -1,11 +0,0 @@ -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn () => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} diff --git a/implementation/13-refactoring/src/Factory/SettingsProvider.php b/implementation/13-refactoring/src/Factory/SettingsProvider.php deleted file mode 100644 index ce1c5f0..0000000 --- a/implementation/13-refactoring/src/Factory/SettingsProvider.php +++ /dev/null @@ -1,10 +0,0 @@ -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - - echo $response->getBody(); - } -} diff --git a/implementation/13-refactoring/src/Http/Emitter.php b/implementation/13-refactoring/src/Http/Emitter.php deleted file mode 100644 index ce4c035..0000000 --- a/implementation/13-refactoring/src/Http/Emitter.php +++ /dev/null @@ -1,10 +0,0 @@ -getAttribute($this->routeAttributeName, false); - assert($handler !== false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} diff --git a/implementation/13-refactoring/src/Http/RouteMiddleware.php b/implementation/13-refactoring/src/Http/RouteMiddleware.php deleted file mode 100644 index e3df6f8..0000000 --- a/implementation/13-refactoring/src/Http/RouteMiddleware.php +++ /dev/null @@ -1,69 +0,0 @@ -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} diff --git a/implementation/13-refactoring/src/Http/RoutedRequestHandler.php b/implementation/13-refactoring/src/Http/RoutedRequestHandler.php deleted file mode 100644 index a7407c9..0000000 --- a/implementation/13-refactoring/src/Http/RoutedRequestHandler.php +++ /dev/null @@ -1,10 +0,0 @@ -routeMiddleware->process($request, $this->handler); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} diff --git a/implementation/13-refactoring/src/Service/Time/Now.php b/implementation/13-refactoring/src/Service/Time/Now.php deleted file mode 100644 index 79224b3..0000000 --- a/implementation/13-refactoring/src/Service/Time/Now.php +++ /dev/null @@ -1,10 +0,0 @@ -engine->render($template, $data); - } -} diff --git a/implementation/13-refactoring/src/Template/Renderer.php b/implementation/13-refactoring/src/Template/Renderer.php deleted file mode 100644 index ff916ed..0000000 --- a/implementation/13-refactoring/src/Template/Renderer.php +++ /dev/null @@ -1,11 +0,0 @@ - $data - */ - public function render(string $template, array $data = []): string; -} diff --git a/implementation/13-refactoring/templates/hello.html b/implementation/13-refactoring/templates/hello.html deleted file mode 100644 index 0e21f2a..0000000 --- a/implementation/13-refactoring/templates/hello.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Hello World - - -

Hello {{name}}

-

The time is {{now}}

- - \ No newline at end of file diff --git a/implementation/14-middleware/.php-cs-fixer.php b/implementation/14-middleware/.php-cs-fixer.php deleted file mode 100644 index 705a7d7..0000000 --- a/implementation/14-middleware/.php-cs-fixer.php +++ /dev/null @@ -1,38 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config' - ]) - ); \ No newline at end of file diff --git a/implementation/14-middleware/.phpcs.xml.dist b/implementation/14-middleware/.phpcs.xml.dist deleted file mode 100644 index 3b433f6..0000000 --- a/implementation/14-middleware/.phpcs.xml.dist +++ /dev/null @@ -1,9 +0,0 @@ - - - - - src - config - - - \ No newline at end of file diff --git a/implementation/14-middleware/composer.json b/implementation/14-middleware/composer.json deleted file mode 100644 index a1372b3..0000000 --- a/implementation/14-middleware/composer.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3", - "mustache/mustache": "^2.14", - "psr/http-server-middleware": "^1.0", - "middlewares/trailing-slash": "^2.0", - "middlewares/whoops": "^2.0" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require-dev": { - "phpstan/phpstan": "^1.5", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0", - "squizlabs/php_codesniffer": "^3.6", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1", - "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0", - "psalm/phar": "^4.22" - }, - "config": { - "allow-plugins": { - "phpstan/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" - } -} diff --git a/implementation/14-middleware/composer.lock b/implementation/14-middleware/composer.lock deleted file mode 100644 index 3ac6853..0000000 --- a/implementation/14-middleware/composer.lock +++ /dev/null @@ -1,1815 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "580bbe25eceb19e89a36dc4e5541d44c", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, - { - "name": "middlewares/trailing-slash", - "version": "v2.0.1", - "source": { - "type": "git", - "url": "https://github.com/middlewares/trailing-slash.git", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", - "shasum": "" - }, - "require": { - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to normalize the trailing slash of the uri path", - "homepage": "https://github.com/middlewares/trailing-slash", - "keywords": [ - "http", - "middleware", - "normalize", - "path", - "psr-15", - "psr-7", - "slash" - ], - "support": { - "issues": "https://github.com/middlewares/trailing-slash/issues", - "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" - }, - "time": "2020-12-02T00:06:55+00:00" - }, - { - "name": "middlewares/utils", - "version": "v3.3.0", - "source": { - "type": "git", - "url": "https://github.com/middlewares/utils.git", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^v2.16", - "guzzlehttp/psr7": "^2.0", - "laminas/laminas-diactoros": "^2.4", - "nyholm/psr7": "^1.0", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "slim/psr7": "^1.4", - "squizlabs/php_codesniffer": "^3.5", - "sunrise/http-message": "^1.0", - "sunrise/http-server-request": "^1.0", - "sunrise/stream": "^1.0.15", - "sunrise/uri": "^1.0.15" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\Utils\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Common utils for PSR-15 middleware packages", - "homepage": "https://github.com/middlewares/utils", - "keywords": [ - "PSR-11", - "http", - "middleware", - "psr-15", - "psr-17", - "psr-7" - ], - "support": { - "issues": "https://github.com/middlewares/utils/issues", - "source": "https://github.com/middlewares/utils/tree/v3.3.0" - }, - "time": "2021-07-04T17:56:23+00:00" - }, - { - "name": "middlewares/whoops", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/middlewares/whoops.git", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", - "shasum": "" - }, - "require": { - "filp/whoops": "^2.5", - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "eloquent/phony-phpunit": "^5.0 || ^7.0", - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to use Whoops as error handler", - "homepage": "https://github.com/middlewares/whoops", - "keywords": [ - "error", - "http", - "middleware", - "psr-15", - "psr-7", - "server", - "whoops" - ], - "support": { - "issues": "https://github.com/middlewares/whoops/issues", - "source": "https://github.com/middlewares/whoops/tree/v2.0.2" - }, - "time": "2022-01-27T20:31:30+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/http-server-middleware", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-middleware.git", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0", - "psr/http-server-handler": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side middleware", - "keywords": [ - "http", - "http-interop", - "middleware", - "psr", - "psr-15", - "psr-7", - "request", - "response" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-middleware/issues", - "source": "https://github.com/php-fig/http-server-middleware/tree/master" - }, - "time": "2018-10-30T17:12:04+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "doctrine/coding-standard", - "version": "8.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/coding-standard.git", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "slevomat/coding-standard": "^6.4.1", - "squizlabs/php_codesniffer": "^3.5.8" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Steve Müller", - "email": "st.mueller@dzh-online.de" - } - ], - "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", - "keywords": [ - "checks", - "code", - "coding", - "cs", - "doctrine", - "rules", - "sniffer", - "sniffs", - "standard", - "style" - ], - "support": { - "issues": "https://github.com/doctrine/coding-standard/issues", - "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" - }, - "time": "2021-04-03T10:54:55+00:00" - }, - { - "name": "mnapoli/hard-mode", - "version": "0.3.0", - "source": { - "type": "git", - "url": "https://github.com/mnapoli/hard-mode.git", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", - "shasum": "" - }, - "require": { - "doctrine/coding-standard": "^8.0" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Hard mode for PHP", - "support": { - "issues": "https://github.com/mnapoli/hard-mode/issues", - "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" - }, - "time": "2020-10-12T07:54:37+00:00" - }, - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.9", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.26", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/master" - }, - "time": "2020-08-03T20:32:43+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", - "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.0" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-24T18:18:00+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^1.2.0" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" - }, - "time": "2021-11-18T09:30:29+00:00" - }, - { - "name": "psalm/phar", - "version": "4.22.0", - "source": { - "type": "git", - "url": "https://github.com/psalm/phar.git", - "reference": "feebed09c9782d9aaa819b794d880c2671ba0e4c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/psalm/phar/zipball/feebed09c9782d9aaa819b794d880c2671ba0e4c", - "reference": "feebed09c9782d9aaa819b794d880c2671ba0e4c", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "vimeo/psalm": "*" - }, - "bin": [ - "psalm.phar" - ], - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer-based Psalm Phar", - "support": { - "issues": "https://github.com/psalm/phar/issues", - "source": "https://github.com/psalm/phar/tree/4.22.0" - }, - "time": "2022-02-27T11:01:37+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "6.4.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpstan/phpstan": "0.12.48", - "phpstan/phpstan-deprecation-rules": "0.12.5", - "phpstan/phpstan-phpunit": "0.12.16", - "phpstan/phpstan-strict-rules": "0.12.5", - "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2020-10-05T12:39:37+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/implementation/14-middleware/config/dependencies.php b/implementation/14-middleware/config/dependencies.php deleted file mode 100644 index e84ad53..0000000 --- a/implementation/14-middleware/config/dependencies.php +++ /dev/null @@ -1,42 +0,0 @@ - fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, - Emitter::class => fn (BasicEmitter $e) => $e, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), - RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), -]; diff --git a/implementation/14-middleware/config/middlewares.php b/implementation/14-middleware/config/middlewares.php deleted file mode 100644 index 71dd461..0000000 --- a/implementation/14-middleware/config/middlewares.php +++ /dev/null @@ -1,11 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); - $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -}; diff --git a/implementation/14-middleware/config/settings.php b/implementation/14-middleware/config/settings.php deleted file mode 100644 index b489400..0000000 --- a/implementation/14-middleware/config/settings.php +++ /dev/null @@ -1,11 +0,0 @@ -aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/14-middleware/public/index.php b/implementation/14-middleware/public/index.php deleted file mode 100644 index d93da3a..0000000 --- a/implementation/14-middleware/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render('hello', $data); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/14-middleware/src/Action/Other.php b/implementation/14-middleware/src/Action/Other.php deleted file mode 100644 index 895796e..0000000 --- a/implementation/14-middleware/src/Action/Other.php +++ /dev/null @@ -1,19 +0,0 @@ -getBody(); - - $body->write('This works too!'); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/14-middleware/src/Bootstrap.php b/implementation/14-middleware/src/Bootstrap.php deleted file mode 100644 index 3abc2e5..0000000 --- a/implementation/14-middleware/src/Bootstrap.php +++ /dev/null @@ -1,40 +0,0 @@ -getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); diff --git a/implementation/14-middleware/src/Exception/InternalServerError.php b/implementation/14-middleware/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/implementation/14-middleware/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ -factory::fromGlobals(); - } - - /** - * @param UriInterface|string $uri - * @param array $serverParams - */ - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} diff --git a/implementation/14-middleware/src/Factory/FileSystemSettingsProvider.php b/implementation/14-middleware/src/Factory/FileSystemSettingsProvider.php deleted file mode 100644 index f071078..0000000 --- a/implementation/14-middleware/src/Factory/FileSystemSettingsProvider.php +++ /dev/null @@ -1,22 +0,0 @@ -filePath; - assert($settings instanceof Settings); - return $settings; - } -} diff --git a/implementation/14-middleware/src/Factory/PipelineProvider.php b/implementation/14-middleware/src/Factory/PipelineProvider.php deleted file mode 100644 index 77738f8..0000000 --- a/implementation/14-middleware/src/Factory/PipelineProvider.php +++ /dev/null @@ -1,25 +0,0 @@ -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} diff --git a/implementation/14-middleware/src/Factory/RequestFactory.php b/implementation/14-middleware/src/Factory/RequestFactory.php deleted file mode 100644 index 2b17abc..0000000 --- a/implementation/14-middleware/src/Factory/RequestFactory.php +++ /dev/null @@ -1,11 +0,0 @@ -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn (): Settings => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} diff --git a/implementation/14-middleware/src/Factory/SettingsProvider.php b/implementation/14-middleware/src/Factory/SettingsProvider.php deleted file mode 100644 index ce1c5f0..0000000 --- a/implementation/14-middleware/src/Factory/SettingsProvider.php +++ /dev/null @@ -1,10 +0,0 @@ -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - - echo $response->getBody(); - } -} diff --git a/implementation/14-middleware/src/Http/ContainerPipeline.php b/implementation/14-middleware/src/Http/ContainerPipeline.php deleted file mode 100644 index 816cedd..0000000 --- a/implementation/14-middleware/src/Http/ContainerPipeline.php +++ /dev/null @@ -1,82 +0,0 @@ - $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} diff --git a/implementation/14-middleware/src/Http/Emitter.php b/implementation/14-middleware/src/Http/Emitter.php deleted file mode 100644 index ce4c035..0000000 --- a/implementation/14-middleware/src/Http/Emitter.php +++ /dev/null @@ -1,10 +0,0 @@ -getAttribute($this->routeAttributeName, false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} diff --git a/implementation/14-middleware/src/Http/Pipeline.php b/implementation/14-middleware/src/Http/Pipeline.php deleted file mode 100644 index 1a9dcda..0000000 --- a/implementation/14-middleware/src/Http/Pipeline.php +++ /dev/null @@ -1,11 +0,0 @@ -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} diff --git a/implementation/14-middleware/src/Http/RoutedRequestHandler.php b/implementation/14-middleware/src/Http/RoutedRequestHandler.php deleted file mode 100644 index a7407c9..0000000 --- a/implementation/14-middleware/src/Http/RoutedRequestHandler.php +++ /dev/null @@ -1,10 +0,0 @@ -pipeline->dispatch($request); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} diff --git a/implementation/14-middleware/src/Service/Time/Now.php b/implementation/14-middleware/src/Service/Time/Now.php deleted file mode 100644 index 79224b3..0000000 --- a/implementation/14-middleware/src/Service/Time/Now.php +++ /dev/null @@ -1,10 +0,0 @@ -engine->render($template, $data); - } -} diff --git a/implementation/14-middleware/src/Template/Renderer.php b/implementation/14-middleware/src/Template/Renderer.php deleted file mode 100644 index ff916ed..0000000 --- a/implementation/14-middleware/src/Template/Renderer.php +++ /dev/null @@ -1,11 +0,0 @@ - $data - */ - public function render(string $template, array $data = []): string; -} diff --git a/implementation/14-middleware/templates/hello.html b/implementation/14-middleware/templates/hello.html deleted file mode 100644 index 0e21f2a..0000000 --- a/implementation/14-middleware/templates/hello.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Hello World - - -

Hello {{name}}

-

The time is {{now}}

- - \ No newline at end of file diff --git a/implementation/15-adding-content/.php-cs-fixer.php b/implementation/15-adding-content/.php-cs-fixer.php deleted file mode 100644 index 705a7d7..0000000 --- a/implementation/15-adding-content/.php-cs-fixer.php +++ /dev/null @@ -1,38 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config' - ]) - ); \ No newline at end of file diff --git a/implementation/15-adding-content/.phpcs.xml.dist b/implementation/15-adding-content/.phpcs.xml.dist deleted file mode 100644 index 3b433f6..0000000 --- a/implementation/15-adding-content/.phpcs.xml.dist +++ /dev/null @@ -1,9 +0,0 @@ - - - - - src - config - - - \ No newline at end of file diff --git a/implementation/15-adding-content/cli-config.php b/implementation/15-adding-content/cli-config.php deleted file mode 100644 index fbc6598..0000000 --- a/implementation/15-adding-content/cli-config.php +++ /dev/null @@ -1,13 +0,0 @@ -getContainer(); - -return ConsoleRunner::createHelperSet($container->get(EntityManagerInterface::class)); diff --git a/implementation/15-adding-content/composer.json b/implementation/15-adding-content/composer.json deleted file mode 100644 index 4809539..0000000 --- a/implementation/15-adding-content/composer.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3", - "mustache/mustache": "^2.14", - "psr/http-server-middleware": "^1.0", - "middlewares/trailing-slash": "^2.0", - "middlewares/whoops": "^2.0", - "erusev/parsedown": "^1.7", - "symfony/cache": "^6.0", - "doctrine/orm": "^2.11", - "league/commonmark": "^2.2" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require-dev": { - "phpstan/phpstan": "^1.5", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0", - "squizlabs/php_codesniffer": "^3.6", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1", - "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0" - }, - "config": { - "allow-plugins": { - "phpstan/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "serve": [ - "Composer\\Config::disableProcessTimeout", - "php -S 0.0.0.0:1234 -t public" - ], - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" - } -} diff --git a/implementation/15-adding-content/composer.lock b/implementation/15-adding-content/composer.lock deleted file mode 100644 index 648f2d5..0000000 --- a/implementation/15-adding-content/composer.lock +++ /dev/null @@ -1,4246 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "1e8469bfebe6479a139b946b8aba49de", - "packages": [ - { - "name": "dflydev/dot-access-data", - "version": "v3.0.1", - "source": { - "type": "git", - "url": "https://github.com/dflydev/dflydev-dot-access-data.git", - "reference": "0992cc19268b259a39e86f296da5f0677841f42c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", - "reference": "0992cc19268b259a39e86f296da5f0677841f42c", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.42", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", - "scrutinizer/ocular": "1.6.0", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^3.14" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Dflydev\\DotAccessData\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dragonfly Development Inc.", - "email": "info@dflydev.com", - "homepage": "http://dflydev.com" - }, - { - "name": "Beau Simensen", - "email": "beau@dflydev.com", - "homepage": "http://beausimensen.com" - }, - { - "name": "Carlos Frutos", - "email": "carlos@kiwing.it", - "homepage": "https://github.com/cfrutos" - }, - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com" - } - ], - "description": "Given a deep data structure, access data by dot notation.", - "homepage": "https://github.com/dflydev/dflydev-dot-access-data", - "keywords": [ - "access", - "data", - "dot", - "notation" - ], - "support": { - "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", - "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" - }, - "time": "2021-08-13T13:06:58+00:00" - }, - { - "name": "doctrine/cache", - "version": "2.1.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/cache.git", - "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/331b4d5dbaeab3827976273e9356b3b453c300ce", - "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce", - "shasum": "" - }, - "require": { - "php": "~7.1 || ^8.0" - }, - "conflict": { - "doctrine/common": ">2.2,<2.4" - }, - "require-dev": { - "alcaeus/mongo-php-adapter": "^1.1", - "cache/integration-tests": "dev-master", - "doctrine/coding-standard": "^8.0", - "mongodb/mongodb": "^1.1", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "predis/predis": "~1.0", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^4.4 || ^5.2 || ^6.0@dev", - "symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev" - }, - "suggest": { - "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", - "homepage": "https://www.doctrine-project.org/projects/cache.html", - "keywords": [ - "abstraction", - "apcu", - "cache", - "caching", - "couchdb", - "memcached", - "php", - "redis", - "xcache" - ], - "support": { - "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/2.1.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", - "type": "tidelift" - } - ], - "time": "2021-07-17T14:49:29+00:00" - }, - { - "name": "doctrine/collections", - "version": "1.6.8", - "source": { - "type": "git", - "url": "https://github.com/doctrine/collections.git", - "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/1958a744696c6bb3bb0d28db2611dc11610e78af", - "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af", - "shasum": "" - }, - "require": { - "php": "^7.1.3 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5", - "vimeo/psalm": "^4.2.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", - "homepage": "https://www.doctrine-project.org/projects/collections.html", - "keywords": [ - "array", - "collections", - "iterators", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/1.6.8" - }, - "time": "2021-08-10T18:51:53+00:00" - }, - { - "name": "doctrine/common", - "version": "3.2.2", - "source": { - "type": "git", - "url": "https://github.com/doctrine/common.git", - "reference": "295082d3750987065912816a9d536c2df735f637" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/295082d3750987065912816a9d536c2df735f637", - "reference": "295082d3750987065912816a9d536c2df735f637", - "shasum": "" - }, - "require": { - "doctrine/persistence": "^2.0", - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "^1.4.1", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", - "squizlabs/php_codesniffer": "^3.0", - "symfony/phpunit-bridge": "^4.0.5", - "vimeo/psalm": "^4.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", - "homepage": "https://www.doctrine-project.org/projects/common.html", - "keywords": [ - "common", - "doctrine", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/common/issues", - "source": "https://github.com/doctrine/common/tree/3.2.2" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", - "type": "tidelift" - } - ], - "time": "2022-02-02T09:15:57+00:00" - }, - { - "name": "doctrine/dbal", - "version": "3.3.4", - "source": { - "type": "git", - "url": "https://github.com/doctrine/dbal.git", - "reference": "83f779beaea1893c0bece093ab2104c6d15a7f26" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/83f779beaea1893c0bece093ab2104c6d15a7f26", - "reference": "83f779beaea1893c0bece093ab2104c6d15a7f26", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2", - "doctrine/cache": "^1.11|^2.0", - "doctrine/deprecations": "^0.5.3", - "doctrine/event-manager": "^1.0", - "php": "^7.3 || ^8.0", - "psr/cache": "^1|^2|^3", - "psr/log": "^1|^2|^3" - }, - "require-dev": { - "doctrine/coding-standard": "9.0.0", - "jetbrains/phpstorm-stubs": "2021.1", - "phpstan/phpstan": "1.4.6", - "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "9.5.16", - "psalm/plugin-phpunit": "0.16.1", - "squizlabs/php_codesniffer": "3.6.2", - "symfony/cache": "^5.2|^6.0", - "symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0", - "vimeo/psalm": "4.22.0" - }, - "suggest": { - "symfony/console": "For helpful console commands such as SQL execution and import of files." - }, - "bin": [ - "bin/doctrine-dbal" - ], - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\DBAL\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - } - ], - "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", - "homepage": "https://www.doctrine-project.org/projects/dbal.html", - "keywords": [ - "abstraction", - "database", - "db2", - "dbal", - "mariadb", - "mssql", - "mysql", - "oci8", - "oracle", - "pdo", - "pgsql", - "postgresql", - "queryobject", - "sasql", - "sql", - "sqlite", - "sqlserver", - "sqlsrv" - ], - "support": { - "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.3.4" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", - "type": "tidelift" - } - ], - "time": "2022-03-20T18:37:29+00:00" - }, - { - "name": "doctrine/deprecations", - "version": "v0.5.3", - "source": { - "type": "git", - "url": "https://github.com/doctrine/deprecations.git", - "reference": "9504165960a1f83cc1480e2be1dd0a0478561314" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314", - "reference": "9504165960a1f83cc1480e2be1dd0a0478561314", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^6.0|^7.0|^8.0", - "phpunit/phpunit": "^7.0|^8.0|^9.0", - "psr/log": "^1.0" - }, - "suggest": { - "psr/log": "Allows logging deprecations via PSR-3 logger implementation" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", - "homepage": "https://www.doctrine-project.org/", - "support": { - "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v0.5.3" - }, - "time": "2021-03-21T12:59:47+00:00" - }, - { - "name": "doctrine/event-manager", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/event-manager.git", - "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", - "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/common": "<2.9@dev" - }, - "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpunit/phpunit": "^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/event-manager.html", - "keywords": [ - "event", - "event dispatcher", - "event manager", - "event system", - "events" - ], - "support": { - "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/1.1.x" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", - "type": "tidelift" - } - ], - "time": "2020-05-29T18:28:51+00:00" - }, - { - "name": "doctrine/inflector", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/doctrine/inflector.git", - "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", - "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^8.2", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "vimeo/psalm": "^4.10" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", - "homepage": "https://www.doctrine-project.org/projects/inflector.html", - "keywords": [ - "inflection", - "inflector", - "lowercase", - "manipulation", - "php", - "plural", - "singular", - "strings", - "uppercase", - "words" - ], - "support": { - "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.4" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", - "type": "tidelift" - } - ], - "time": "2021-10-22T20:16:43+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2022-03-03T08:28:38+00:00" - }, - { - "name": "doctrine/lexer", - "version": "1.2.3", - "source": { - "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.11" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", - "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.2.3" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" - } - ], - "time": "2022-02-28T11:07:21+00:00" - }, - { - "name": "doctrine/orm", - "version": "2.11.2", - "source": { - "type": "git", - "url": "https://github.com/doctrine/orm.git", - "reference": "9c351e044478135aec1755e2c0c0493a4b6309db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/9c351e044478135aec1755e2c0c0493a4b6309db", - "reference": "9c351e044478135aec1755e2c0c0493a4b6309db", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2", - "doctrine/cache": "^1.12.1 || ^2.1.1", - "doctrine/collections": "^1.5", - "doctrine/common": "^3.0.3", - "doctrine/dbal": "^2.13.1 || ^3.2", - "doctrine/deprecations": "^0.5.3", - "doctrine/event-manager": "^1.1", - "doctrine/inflector": "^1.4 || ^2.0", - "doctrine/instantiator": "^1.3", - "doctrine/lexer": "^1.0", - "doctrine/persistence": "^2.2", - "ext-ctype": "*", - "php": "^7.1 || ^8.0", - "psr/cache": "^1 || ^2 || ^3", - "symfony/console": "^3.0 || ^4.0 || ^5.0 || ^6.0", - "symfony/polyfill-php72": "^1.23", - "symfony/polyfill-php80": "^1.15" - }, - "conflict": { - "doctrine/annotations": "<1.13 || >= 2.0" - }, - "require-dev": { - "doctrine/annotations": "^1.13", - "doctrine/coding-standard": "^9.0", - "phpbench/phpbench": "^0.16.10 || ^1.0", - "phpstan/phpstan": "1.4.6", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", - "squizlabs/php_codesniffer": "3.6.2", - "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0", - "vimeo/psalm": "4.22.0" - }, - "suggest": { - "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0", - "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" - }, - "bin": [ - "bin/doctrine" - ], - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\ORM\\": "lib/Doctrine/ORM" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "Object-Relational-Mapper for PHP", - "homepage": "https://www.doctrine-project.org/projects/orm.html", - "keywords": [ - "database", - "orm" - ], - "support": { - "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/2.11.2" - }, - "time": "2022-03-09T15:23:58+00:00" - }, - { - "name": "doctrine/persistence", - "version": "2.4.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/persistence.git", - "reference": "092a52b71410ac1795287bb5135704ef07d18dd0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/092a52b71410ac1795287bb5135704ef07d18dd0", - "reference": "092a52b71410ac1795287bb5135704ef07d18dd0", - "shasum": "" - }, - "require": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/collections": "^1.0", - "doctrine/deprecations": "^0.5.3", - "doctrine/event-manager": "^1.0", - "php": "^7.1 || ^8.0", - "psr/cache": "^1.0 || ^2.0 || ^3.0" - }, - "conflict": { - "doctrine/annotations": "<1.0 || >=2.0", - "doctrine/common": "<2.10" - }, - "require-dev": { - "composer/package-versions-deprecated": "^1.11", - "doctrine/annotations": "^1.0", - "doctrine/coding-standard": "^9.0", - "doctrine/common": "^3.0", - "phpstan/phpstan": "1.4.6", - "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.5", - "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "vimeo/psalm": "4.21.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "src/Common", - "Doctrine\\Persistence\\": "src/Persistence" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", - "homepage": "https://doctrine-project.org/projects/persistence.html", - "keywords": [ - "mapper", - "object", - "odm", - "orm", - "persistence" - ], - "support": { - "issues": "https://github.com/doctrine/persistence/issues", - "source": "https://github.com/doctrine/persistence/tree/2.4.1" - }, - "time": "2022-03-22T06:44:40+00:00" - }, - { - "name": "erusev/parsedown", - "version": "1.7.4", - "source": { - "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35" - }, - "type": "library", - "autoload": { - "psr-0": { - "Parsedown": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com" - } - ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org", - "keywords": [ - "markdown", - "parser" - ], - "support": { - "issues": "https://github.com/erusev/parsedown/issues", - "source": "https://github.com/erusev/parsedown/tree/1.7.x" - }, - "time": "2019-12-30T22:54:17+00:00" - }, - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.9.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2022-03-29T20:12:16+00:00" - }, - { - "name": "league/commonmark", - "version": "2.2.3", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/commonmark.git", - "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", - "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "league/config": "^1.1.1", - "php": "^7.4 || ^8.0", - "psr/event-dispatcher": "^1.0", - "symfony/deprecation-contracts": "^2.1 || ^3.0", - "symfony/polyfill-php80": "^1.15" - }, - "require-dev": { - "cebe/markdown": "^1.0", - "commonmark/cmark": "0.30.0", - "commonmark/commonmark.js": "0.30.0", - "composer/package-versions-deprecated": "^1.8", - "erusev/parsedown": "^1.0", - "ext-json": "*", - "github/gfm": "0.29.0", - "michelf/php-markdown": "^1.4", - "phpstan/phpstan": "^0.12.88 || ^1.0.0", - "phpunit/phpunit": "^9.5.5", - "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", - "unleashedtech/php-coding-standard": "^3.1", - "vimeo/psalm": "^4.7.3" - }, - "suggest": { - "symfony/yaml": "v2.3+ required if using the Front Matter extension" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.3-dev" - } - }, - "autoload": { - "psr-4": { - "League\\CommonMark\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com", - "role": "Lead Developer" - } - ], - "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", - "homepage": "https://commonmark.thephpleague.com", - "keywords": [ - "commonmark", - "flavored", - "gfm", - "github", - "github-flavored", - "markdown", - "md", - "parser" - ], - "support": { - "docs": "https://commonmark.thephpleague.com/", - "forum": "https://github.com/thephpleague/commonmark/discussions", - "issues": "https://github.com/thephpleague/commonmark/issues", - "rss": "https://github.com/thephpleague/commonmark/releases.atom", - "source": "https://github.com/thephpleague/commonmark" - }, - "funding": [ - { - "url": "https://www.colinodell.com/sponsor", - "type": "custom" - }, - { - "url": "https://www.paypal.me/colinpodell/10.00", - "type": "custom" - }, - { - "url": "https://github.com/colinodell", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/commonmark", - "type": "tidelift" - } - ], - "time": "2022-02-26T21:24:45+00:00" - }, - { - "name": "league/config", - "version": "v1.1.1", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/config.git", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "shasum": "" - }, - "require": { - "dflydev/dot-access-data": "^3.0.1", - "nette/schema": "^1.2", - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.90", - "phpunit/phpunit": "^9.5.5", - "scrutinizer/ocular": "^1.8.1", - "unleashedtech/php-coding-standard": "^3.1", - "vimeo/psalm": "^4.7.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.2-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Config\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com", - "role": "Lead Developer" - } - ], - "description": "Define configuration arrays with strict schemas and access values with dot notation", - "homepage": "https://config.thephpleague.com", - "keywords": [ - "array", - "config", - "configuration", - "dot", - "dot-access", - "nested", - "schema" - ], - "support": { - "docs": "https://config.thephpleague.com/", - "issues": "https://github.com/thephpleague/config/issues", - "rss": "https://github.com/thephpleague/config/releases.atom", - "source": "https://github.com/thephpleague/config" - }, - "funding": [ - { - "url": "https://www.colinodell.com/sponsor", - "type": "custom" - }, - { - "url": "https://www.paypal.me/colinpodell/10.00", - "type": "custom" - }, - { - "url": "https://github.com/colinodell", - "type": "github" - } - ], - "time": "2021-08-14T12:15:32+00:00" - }, - { - "name": "middlewares/trailing-slash", - "version": "v2.0.1", - "source": { - "type": "git", - "url": "https://github.com/middlewares/trailing-slash.git", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", - "shasum": "" - }, - "require": { - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to normalize the trailing slash of the uri path", - "homepage": "https://github.com/middlewares/trailing-slash", - "keywords": [ - "http", - "middleware", - "normalize", - "path", - "psr-15", - "psr-7", - "slash" - ], - "support": { - "issues": "https://github.com/middlewares/trailing-slash/issues", - "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" - }, - "time": "2020-12-02T00:06:55+00:00" - }, - { - "name": "middlewares/utils", - "version": "v3.3.0", - "source": { - "type": "git", - "url": "https://github.com/middlewares/utils.git", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^v2.16", - "guzzlehttp/psr7": "^2.0", - "laminas/laminas-diactoros": "^2.4", - "nyholm/psr7": "^1.0", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "slim/psr7": "^1.4", - "squizlabs/php_codesniffer": "^3.5", - "sunrise/http-message": "^1.0", - "sunrise/http-server-request": "^1.0", - "sunrise/stream": "^1.0.15", - "sunrise/uri": "^1.0.15" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\Utils\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Common utils for PSR-15 middleware packages", - "homepage": "https://github.com/middlewares/utils", - "keywords": [ - "PSR-11", - "http", - "middleware", - "psr-15", - "psr-17", - "psr-7" - ], - "support": { - "issues": "https://github.com/middlewares/utils/issues", - "source": "https://github.com/middlewares/utils/tree/v3.3.0" - }, - "time": "2021-07-04T17:56:23+00:00" - }, - { - "name": "middlewares/whoops", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/middlewares/whoops.git", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", - "shasum": "" - }, - "require": { - "filp/whoops": "^2.5", - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "eloquent/phony-phpunit": "^5.0 || ^7.0", - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to use Whoops as error handler", - "homepage": "https://github.com/middlewares/whoops", - "keywords": [ - "error", - "http", - "middleware", - "psr-15", - "psr-7", - "server", - "whoops" - ], - "support": { - "issues": "https://github.com/middlewares/whoops/issues", - "source": "https://github.com/middlewares/whoops/tree/v2.0.2" - }, - "time": "2022-01-27T20:31:30+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nette/schema", - "version": "v1.2.2", - "source": { - "type": "git", - "url": "https://github.com/nette/schema.git", - "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", - "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", - "shasum": "" - }, - "require": { - "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": ">=7.1 <8.2" - }, - "require-dev": { - "nette/tester": "^2.3 || ^2.4", - "phpstan/phpstan-nette": "^0.12", - "tracy/tracy": "^2.7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "📐 Nette Schema: validating data structures against a given Schema.", - "homepage": "https://nette.org", - "keywords": [ - "config", - "nette" - ], - "support": { - "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.2" - }, - "time": "2021-10-15T11:40:02+00:00" - }, - { - "name": "nette/utils", - "version": "v3.2.7", - "source": { - "type": "git", - "url": "https://github.com/nette/utils.git", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", - "shasum": "" - }, - "require": { - "php": ">=7.2 <8.2" - }, - "conflict": { - "nette/di": "<3.0.6" - }, - "require-dev": { - "nette/tester": "~2.0", - "phpstan/phpstan": "^1.0", - "tracy/tracy": "^2.3" - }, - "suggest": { - "ext-gd": "to use Image", - "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", - "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", - "ext-json": "to use Nette\\Utils\\Json", - "ext-mbstring": "to use Strings::lower() etc...", - "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", - "ext-xml": "to use Strings::length() etc. when mbstring is not available" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", - "homepage": "https://nette.org", - "keywords": [ - "array", - "core", - "datetime", - "images", - "json", - "nette", - "paginator", - "password", - "slugify", - "string", - "unicode", - "utf-8", - "utility", - "validation" - ], - "support": { - "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.2.7" - }, - "time": "2022-01-24T11:29:14+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/cache", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" - }, - "time": "2021-02-03T23:26:27+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/http-server-middleware", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-middleware.git", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0", - "psr/http-server-handler": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side middleware", - "keywords": [ - "http", - "http-interop", - "middleware", - "psr", - "psr-15", - "psr-7", - "request", - "response" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-middleware/issues", - "source": "https://github.com/php-fig/http-server-middleware/tree/master" - }, - "time": "2018-10-30T17:12:04+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - }, - { - "name": "symfony/cache", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache.git", - "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", - "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "psr/cache": "^2.0|^3.0", - "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^1.1.7|^2|^3", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/var-exporter": "^5.4|^6.0" - }, - "conflict": { - "doctrine/dbal": "<2.13.1", - "symfony/dependency-injection": "<5.4", - "symfony/http-kernel": "<5.4", - "symfony/var-dumper": "<5.4" - }, - "provide": { - "psr/cache-implementation": "2.0|3.0", - "psr/simple-cache-implementation": "1.0|2.0|3.0", - "symfony/cache-implementation": "1.1|2.0|3.0" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/dbal": "^2.13.1|^3.0", - "predis/predis": "^1.1", - "psr/simple-cache": "^1.0|^2.0|^3.0", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/filesystem": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/messenger": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Cache\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", - "homepage": "https://symfony.com", - "keywords": [ - "caching", - "psr6" - ], - "support": { - "source": "https://github.com/symfony/cache/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "symfony/cache-contracts", - "version": "v3.0.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache-contracts.git", - "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/2f7463f156cf9c665d9317e21a809c3bbff5754e", - "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "psr/cache": "^3.0" - }, - "suggest": { - "symfony/cache-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Cache\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to caching", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.0.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-08-17T15:35:52+00:00" - }, - { - "name": "symfony/console", - "version": "v6.0.5", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "3bebf4108b9e07492a2a4057d207aa5a77d146b1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/3bebf4108b9e07492a2a4057d207aa5a77d146b1", - "reference": "3bebf4108b9e07492a2a4057d207aa5a77d146b1", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.4|^6.0" - }, - "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/lock": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command line", - "console", - "terminal" - ], - "support": { - "source": "https://github.com/symfony/console/tree/v6.0.5" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-02-25T10:48:52+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v2.5.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8", - "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-07-12T14:48:14+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "30885182c981ab175d4d034db0f6f469898070ab" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", - "reference": "30885182c981ab175d4d034db0f6f469898070ab", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-10-20T20:35:02+00:00" - }, - { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "81b86b50cf841a64252b439e738e97f4a34e2783" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", - "reference": "81b86b50cf841a64252b439e738e97f4a34e2783", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-23T21:10:46+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-02-19T12:13:01+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/polyfill-php72", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", - "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-05-27T09:17:38+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-04T08:16:47+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v2.5.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", - "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "suggest": { - "symfony/service-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-04T16:48:04+00:00" - }, - { - "name": "symfony/string", - "version": "v6.0.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/522144f0c4c004c80d56fa47e40e17028e2eefc2", - "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/translation-contracts": "<2.0" - }, - "require-dev": { - "symfony/error-handler": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/translation-contracts": "^2.0|^3.0", - "symfony/var-exporter": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "files": [ - "Resources/functions.php" - ], - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "source": "https://github.com/symfony/string/tree/v6.0.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:55:41+00:00" - }, - { - "name": "symfony/var-exporter", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-exporter.git", - "reference": "130229a482abf17635a685590958894dfb4b4360" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/130229a482abf17635a685590958894dfb4b4360", - "reference": "130229a482abf17635a685590958894dfb4b4360", - "shasum": "" - }, - "require": { - "php": ">=8.0.2" - }, - "require-dev": { - "symfony/var-dumper": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\VarExporter\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows exporting any serializable PHP data structure to plain PHP code", - "homepage": "https://symfony.com", - "keywords": [ - "clone", - "construct", - "export", - "hydrate", - "instantiate", - "serialize" - ], - "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "doctrine/coding-standard", - "version": "8.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/coding-standard.git", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "slevomat/coding-standard": "^6.4.1", - "squizlabs/php_codesniffer": "^3.5.8" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Steve Müller", - "email": "st.mueller@dzh-online.de" - } - ], - "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", - "keywords": [ - "checks", - "code", - "coding", - "cs", - "doctrine", - "rules", - "sniffer", - "sniffs", - "standard", - "style" - ], - "support": { - "issues": "https://github.com/doctrine/coding-standard/issues", - "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" - }, - "time": "2021-04-03T10:54:55+00:00" - }, - { - "name": "mnapoli/hard-mode", - "version": "0.3.0", - "source": { - "type": "git", - "url": "https://github.com/mnapoli/hard-mode.git", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", - "shasum": "" - }, - "require": { - "doctrine/coding-standard": "^8.0" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Hard mode for PHP", - "support": { - "issues": "https://github.com/mnapoli/hard-mode/issues", - "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" - }, - "time": "2020-10-12T07:54:37+00:00" - }, - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.9", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.26", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/master" - }, - "time": "2020-08-03T20:32:43+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.5.3", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/39953ac1452a8843702ee41a35b4861d3e8207a7", - "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.3" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-30T21:55:08+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^1.2.0" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" - }, - "time": "2021-11-18T09:30:29+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "6.4.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpstan/phpstan": "0.12.48", - "phpstan/phpstan-deprecation-rules": "0.12.5", - "phpstan/phpstan-phpunit": "0.12.16", - "phpstan/phpstan-strict-rules": "0.12.5", - "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2020-10-05T12:39:37+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.3.0" -} diff --git a/implementation/15-adding-content/config/dependencies.php b/implementation/15-adding-content/config/dependencies.php deleted file mode 100644 index e2a3925..0000000 --- a/implementation/15-adding-content/config/dependencies.php +++ /dev/null @@ -1,60 +0,0 @@ - fn (SystemClockNow $n) => $n, - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, - Emitter::class => fn (BasicEmitter $e) => $e, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, - CacheInterface::class => fn (FilesystemAdapter $a) => $a, - MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, - MarkdownParser::class => fn (ParsedownParser $p) => $p, - - // Factories - ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), - Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - MarkdownPageFilesystem::class => fn (Settings $s) => new MarkdownPageFilesystem($s->pagesPath), - CachedMarkdownPageRepo::class => fn (CacheInterface $c, MarkdownPageFilesystem $r, Settings $s) => new CachedMarkdownPageRepo($c, $r, $s), - EntityManagerInterface::class => fn (DoctrineEm $f) => $f->create(), -]; diff --git a/implementation/15-adding-content/config/middlewares.php b/implementation/15-adding-content/config/middlewares.php deleted file mode 100644 index 71dd461..0000000 --- a/implementation/15-adding-content/config/middlewares.php +++ /dev/null @@ -1,11 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/page', [Page::class, 'list']); - $r->addRoute('GET', '/page/{page}', [Page::class, 'show']); - $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); - $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -}; diff --git a/implementation/15-adding-content/config/settings.php b/implementation/15-adding-content/config/settings.php deleted file mode 100644 index 5c58216..0000000 --- a/implementation/15-adding-content/config/settings.php +++ /dev/null @@ -1,23 +0,0 @@ - 'pdo_sqlite', - 'user' => '', - 'password' => '', - 'path' => __DIR__ . '/../data/db.sqlite', - ], - doctrine: [ - 'devMode' => true, - 'metadataDirs' => [__DIR__ . '/../src/Model/'], - 'cacheDir' => __DIR__ . '/../data/cache/', - ], -); diff --git a/implementation/15-adding-content/data/pages/01-front-controller.md b/implementation/15-adding-content/data/pages/01-front-controller.md deleted file mode 100644 index 87a12ad..0000000 --- a/implementation/15-adding-content/data/pages/01-front-controller.md +++ /dev/null @@ -1,53 +0,0 @@ -[next >>](02-composer.md) - -### Front Controller - -A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. - -To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. - -A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. - -The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. - -But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. - -So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. - -Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: - -```php ->](02-composer.md) diff --git a/implementation/15-adding-content/data/pages/02-composer.md b/implementation/15-adding-content/data/pages/02-composer.md deleted file mode 100644 index a25a4a8..0000000 --- a/implementation/15-adding-content/data/pages/02-composer.md +++ /dev/null @@ -1,75 +0,0 @@ -[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) - -### Composer - -[Composer](https://getcomposer.org/) is a dependency manager for PHP. - -Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do -something. With Composer, you can install third-party libraries for your application. - -If you don't have Composer installed already, head over to the website and install it. You can find Composer packages -for your project on [Packagist](https://packagist.org/). - -Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will -be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. - -Add the following content to the file: - -```json -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubiana", - "email": "lubiana@hannover.ccc.de" - } - ] -} -``` - -In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use -whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. -Just replace it with your namespace in your own code. - -I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. - -As the Bootstrap.php file is placed in that directory we should -add the namespace to the File as well. Here is my current Bootstrap.php -as a reference: - -```php ->](03-error-handler.md) diff --git a/implementation/15-adding-content/data/pages/03-error-handler.md b/implementation/15-adding-content/data/pages/03-error-handler.md deleted file mode 100644 index 60465d0..0000000 --- a/implementation/15-adding-content/data/pages/03-error-handler.md +++ /dev/null @@ -1,79 +0,0 @@ -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) - -### Error Handler - -An error handler allows you to customize what happens if your code results in an error. - -A nice error page with a lot of information for debugging goes a long way during development. So the first package -for your application will take care of that. - -I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. -If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, -you have total control over your project. - -An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) - -To install a new package, open up your `composer.json` and add the package to the require part. It should now look -like this: - -```php -"require": { - "php": ">=8.1.0", - "filp/whoops": "^2.14" -}, -``` - -Now run `composer update` in your console and it will be installed. - -Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, -i that case composer automatically installs the package and updates your composer.json-file. - -But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, -ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you -only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. - -**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message -can help someone to gain access to your system. Always show a user friendly error page instead and send an email to -yourself, write to a log or something similar. So only you can see the errors in the production environment. - -For development that does not make sense though -- you want a nice error page. The solution is to have an environment -switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in -case no environment has been set. - -Then after the error handler registration, throw an `Exception` to test if everything is working correctly. -Your `Bootstrap.php` should now look similar to this: - -```php -pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (\Throwable $e) { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -throw new \Exception("Ooooopsie"); - -``` - -You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until -you get it working. Now would also be a good time for another commit. - - -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/implementation/15-adding-content/data/pages/04-development-helpers.md b/implementation/15-adding-content/data/pages/04-development-helpers.md deleted file mode 100644 index 74f913c..0000000 --- a/implementation/15-adding-content/data/pages/04-development-helpers.md +++ /dev/null @@ -1,260 +0,0 @@ -[<< previous](03-error-handler.md) | [next >>](05-http.md) - -### Development Helpers - -I have added some more helpers to my composer.json that help me with development. As these are scripts and programms -used only for development they should not be used in a production environment. Composer has a specific sections in its -file called "dev-dependencies", everything that is required in this section does not get installen in production. - -Let's install our dev-helpers and i will explain them one by one: -`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` - -#### Static Code Analysis with phpstan - -Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or -create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements -that are not (or not always) available, if-statements that always are true and a lot of other stuff. - -A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. - -```php -/** - * @param \DateTime $date - * @return void - */ -function printDate($date) { - $date->format('Y-m-d H:i:s'); -} - -printDate('now'); -``` -if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` - -It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has -no use, and secondly, that we are calling the function with a string instead of a datetime object. - -```shell -1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - ------ --------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ --------------------------------------------------------------------------------------------- -30 Call to method DateTime::format() on a separate line has no effect. -33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. - ------ --------------------------------------------------------------------------------------------- -``` - -The second error is something that "declare strict-types" already catches for us, but the first error is something that -we usually would not discover easily without speccially looking for this errortype. - -We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and -path everytime we want to check our code for errors: - -```yaml -parameters: - level: max - paths: - - src -``` -now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project - -With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us -some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. - -```shell -composer require --dev phpstan/extension-installer -composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules -``` - -During the first install you need to allow the extension installer to actually install the extension. The second command -installs some more strict rulesets and activates them in phpstan. - -If we now rerun phpstan it already tells us about some errors we have made: - -``` - ------ ----------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ ----------------------------------------------------------------------------------------------- -10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider - using long ternary. -25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: - http://bit.ly/subtypeexception -26 Unreachable statement - code above always terminates. - ------ ----------------------------------------------------------------------------------------------- -``` - -The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove -that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific -problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working -on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages -everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we -are adding to our code. - -In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the -phpstan.neon file and run phpstan with the -'--generate-baseline' option: - -```yaml -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` -```shell -[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline -Note: Using configuration file /home/vagrant/app/phpstan.neon. - 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - - - [OK] Baseline generated with 1 error. - - -``` - -you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) - -#### PHP-CS-Fixer - -Another great tool is the php-cs-fixer, which just applies a specific style to your code. - -when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current -directory. - -You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) - -personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup -a configuration file that i use in all my projects .php-cs-fixer.php: - -```php -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - ]) - ); -``` - -#### PHP Codesniffer - -The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra -rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. - -it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. - -Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have -guessed we are adding some extra rules. - -Lets install the ruleset with composer -```shell -composer require --dev mnapoli/hard-mode -``` - -and add a configuration file to actually use it '.phpcs.xml.dist' -```xml - - - - - src - - - -``` - -running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. - -``` -[vagrant@archlinux app]$ ./vendor/bin/phpcs - -FILE: src/Bootstrap.php ----------------------------------------------------------------------------------------------------- -FOUND 4 ERRORS AFFECTING 4 LINES ----------------------------------------------------------------------------------------------------- - 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. - 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead ----------------------------------------------------------------------------------------------------- -PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY ----------------------------------------------------------------------------------------------------- - -Time: 639ms; Memory: 10MB -``` - -You can then use `./vendor/bin/phpcbf` to try to fix them - - -#### Symfony Var-Dumper - -another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small -functions. - -dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects -or arrays. - -dd() on the other hand is a function that dumps its parameters and then exits the php-script. - -you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. - -#### Composer scripts - -now we have a few commands that are available on the command line. i personally do not like to type complex commands -with lots of parameters by hand all the time, so i added a few lines to my composer.json: - -```json -"scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" -}, -``` - -that way i can just type "composer" followed by the command name in the root of my project. if i want to start the -php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the -time. - -You could also configure PhpStorm to automatically run these commands in the background and highlight the violations -directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my -flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. - -My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before -commiting and pushing the code. - -[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/implementation/15-adding-content/data/pages/05-http.md b/implementation/15-adding-content/data/pages/05-http.md deleted file mode 100644 index 6166214..0000000 --- a/implementation/15-adding-content/data/pages/05-http.md +++ /dev/null @@ -1,124 +0,0 @@ -[<< previous](04-development-helpers.md) | [next >>](06-router.md) - -### HTTP - -PHP already has a few things built in to make working with HTTP easier. For example there are the -[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. - -These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, -if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, -then you will want a class with a nice object-oriented interface that you can use in your application instead. - -Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The -standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php -projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. - -As this is a widely adopted standard there are already several implementations available for us to use. I will choose -the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. - -Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a -[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. - -Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use -that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding -the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). - - -to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit -enter - -Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): - -```php -$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); -$response = new \Laminas\Diactoros\Response; -$response->getBody()->write('Hello World! '); -$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); -``` - -This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. - -In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the -write()-Method on that object. - - -To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: - -```php -echo $response->getBody(); -``` - -This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only -stores data. - -You can play around with the other methods of the Request object and take a look at its content with the dd() function. - -```php -dd($response) -``` - -Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot -be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which -creates a copy of the request object with the changed property and returns that clone: - -```php -$response = $response->withStatus(200); -$response = $response->withAddedHeader('Content-type', 'application/json'); -``` - -If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been -defined this way. - -But if you have been keeping attention you might argue that the following line should not work if the request object is -immutable. - -```php -$response->getBody()->write('Hello World!'); -``` - -The response-body implements a stream interface which is immutable for some reasons that are described in the -[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be -aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in -the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. -I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content -to a response object. - -```php -$body = $response->getBody(); -$body->write('Hello World!'); -$response = $response->withBody($body); -``` - -Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our -output-logic a little bit more. Replace the line that echos the response-body with the following: - -```php -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); -``` - -This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a -webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. - -Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. - -Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter - - -[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/implementation/15-adding-content/data/pages/06-router.md b/implementation/15-adding-content/data/pages/06-router.md deleted file mode 100644 index 6c39ae5..0000000 --- a/implementation/15-adding-content/data/pages/06-router.md +++ /dev/null @@ -1,101 +0,0 @@ -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) - -### Router - -A router dispatches to different handlers depending on rules that you have set up. - -With your current setup it does not matter what URL is used to access the application, it will always result in the same -response. So let's fix that now. - -I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own -favorite package. - -Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) - -By now you know how to install Composer packages, so I will leave that to you. - -Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. - -```php -$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -switch ($routeInfo[0]) { - case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: - $response = (new \Laminas\Diactoros\Response)->withStatus(405); - $response->getBody()->write('Method not allowed'); - $response = $response->withStatus(405); - break; - case \FastRoute\Dispatcher::FOUND: - $handler = $routeInfo[1]; - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - /** @var \Psr\Http\Message\ResponseInterface $response */ - $response = call_user_func($handler, $request); - break; - case \FastRoute\Dispatcher::NOT_FOUND: - default: - $response = (new \Laminas\Diactoros\Response)->withStatus(404); - $response->getBody()->write('Not Found!'); - break; -} -``` - -In the first part of the code, you are registering the available routes for your application. In the second part, the -dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, -we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. -If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. - -This setup might work for really small applications, but once you start adding a few routes your bootstrap file will -quickly get cluttered. So let's move them out into a separate file. - -Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; - -```php -addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}; -``` - -Now let's rewrite the route dispatcher part to use the `Routes.php` file. - -```php -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); -``` - -This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. - -Of course we now need to add the 'config' folder to the configuration files of our -devhelpers so that they can scan that directory as well. - -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/implementation/15-adding-content/data/pages/07-dispatching-to-a-class.md b/implementation/15-adding-content/data/pages/07-dispatching-to-a-class.md deleted file mode 100644 index 0c961a4..0000000 --- a/implementation/15-adding-content/data/pages/07-dispatching-to-a-class.md +++ /dev/null @@ -1,137 +0,0 @@ -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) - -### Dispatching to a Class - -In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). -MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to -learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) -and the followup posts. - -So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). - -We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other -common names are 'Controllers' or 'Actions'. - -Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. -In there, create a `Hello.php` file. - -```php -getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - } -} -``` - -You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) -that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is -fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement -it in some other parts of our application as well. In order to use that Interface we have to require it with composer: -'composer require psr/http-server-handler'. - -The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. -At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. - -Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: - -```php -return function(\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); - $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); -}; -``` - -Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared -the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing -the response to the one we defined for the second route. - -To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: - -```php -case \FastRoute\Dispatcher::FOUND: - $handler = new $routeInfo[1]; - if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { - throw new \Exception('Invalid Requesthandler'); - } - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - assert($response instanceof \Psr\Http\Message\ResponseInterface) - break; -``` - -So instead of just calling a method you are now instantiating an object and then calling the method on it. - -Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. - -And of course don't forget to commit your changes. - -Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still -generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for -example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still -have our whoopsie error-handler but i like to be more explicit in my control flow. - -In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new -Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and -InternalServerError. All three should extend phps Base Exception class. - -Here is my NotFound.php for example. - -```php - $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - break; - case Dispatcher::METHOD_NOT_ALLOWED: - throw new MethodNotAllowed; - - case Dispatcher::NOT_FOUND: - default: - throw new NotFound; - } -} catch (MethodNotAllowed) { - $response = (new Response)->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = (new Response)->withStatus(404); - $response->getBody()->write('Not Found'); -} catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); -} -``` - -Check if our code still works, try to trigger some errors, run phpstan and the fix command -and don't forget to commit your changes. - -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/implementation/15-adding-content/data/pages/08-inversion-of-control.md b/implementation/15-adding-content/data/pages/08-inversion-of-control.md deleted file mode 100644 index 21f4f23..0000000 --- a/implementation/15-adding-content/data/pages/08-inversion-of-control.md +++ /dev/null @@ -1,54 +0,0 @@ -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) - -### Inversion of Control - -In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we -want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then -we would need to edit every one of our request handlers to call a different constructor of the class. - -The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that -instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done -with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). - -If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is -implemented, it will make sense. - -Change your `Hello` action to the following: - -```php -getAttribute('name', 'Stranger'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: - -```php -$handler = new $className($response); -``` - -Of course we need to also update all the other handlers. - -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/implementation/15-adding-content/data/pages/09-dependency-injector.md b/implementation/15-adding-content/data/pages/09-dependency-injector.md deleted file mode 100644 index 7f7c6a2..0000000 --- a/implementation/15-adding-content/data/pages/09-dependency-injector.md +++ /dev/null @@ -1,213 +0,0 @@ -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) - -### Dependency Injector - -A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when -the class is instantiated. - -Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work -with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look -for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). - -I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) -out of the box. - -After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: - -```php -addDefinitions([ - \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), - \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), -]); - -return $builder->build(); -``` - -In this file we create a containerbuilder, add some definitions to it and return the container. -As the container supports autowiring we only need to define services where we want to use a specific implementation of -an interface. - -In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to -tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second -exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. - -Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), -as we will use that extensively. - -Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally -to create our Handler-Object - -```php -$container = require __DIR__ . '/../config/dependencies.php'; -assert($container instanceof \Psr\Container\ContainerInterface); - -$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); -assert($request instanceof \Psr\Http\Message\ServerRequestInterface); -``` - -The other part that has to be changed is the dispatching of the route. Before you had the following code: - -```php -$className = $routeInfo[1]; -$handler = new $className($response); -assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Change that to the following: - -```php -/** @var RequestHandlerInterface $handler */ -$className = $routeInfo[1]; -$handler = $container->get($className); -assert($handler instanceof RequestHandlerInterface); -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Make sure to use the container fetch the response object in the catch blocks as well: - -```php -} catch (MethodNotAllowed) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(404); - $response->getBody()->write('Not Found'); -} -``` - -Now all your controller constructor dependencies will be automatically resolved with PHP-DI. - -We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons -in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call -`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we -want to work with a different date in a test. - -Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation -that creates us a DateTimeImmutable object with the current date and time. - -src/Service/Time/Now.php: -```php -namespace Lubian\NoFramework\Service\Time; - -interface Now -{ - public function __invoke(): \DateTimeImmutable; -} -``` -src/Service/Time/SystemClockNow.php: -```php -namespace Lubian\NoFramework\Service\Time; - -final class SystemClockNow implements Now -{ - - public function __invoke(): \DateTimeImmutable - { - return new \DateTimeImmutable; - } -} -``` -If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and -update the handle-method to use the new class property: - -```php -getAttribute('name', 'Stranger'); - $nowAsString = ($this->now)()->format('H:i:s'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowAsString); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI -automatically figures out what classes are requested in the constructor and tries to create the objects needed. - -But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred -S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: - -```php - public function __construct( - private ResponseInterface $response, - private Now $now, - ) -``` - -When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation -should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: - -```php -\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), -``` - -we could also use the PHP-DI create method to delegate the object creation to the container implementation: -```php -\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), -``` - -this way the container can try to resolve any dependencies that the class might have internally, but prefer the other -method because we are not depending on this specific dependency injection implementation. - -Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are -requesting the Hello action. - -If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As -we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way -we wont get any warnings for this particular issue, but for any other issues we add to our code. - -Update the phpstan.neon file to include a "baseline" file: - -``` -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` - -if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and -ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just -'baseline' - -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/implementation/15-adding-content/data/pages/10-invoker.md b/implementation/15-adding-content/data/pages/10-invoker.md deleted file mode 100644 index 3033fae..0000000 --- a/implementation/15-adding-content/data/pages/10-invoker.md +++ /dev/null @@ -1,102 +0,0 @@ -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) - -### Invoker - -Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the -one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long -with some services and not the whole Requestobject with all its various properties. - -If we take our Hello action for example we only need a response object, the time service and the 'name' information from -the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named -the class hello and it would be redundant to also call the the method hello. So an updated version of that class could -look like this: - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - string $name = 'Stranger', - ): ResponseInterface - { - $body = $this->response->getBody(); - $nowString = $now->get()->format('H:i:s'); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowString); - return $response - ->withBody($body) - ->withStatus(200); - } -} -``` - -It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short -closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the -rootpath of our application yet. - -```php -$r->addRoute('GET', '/hello[/{name}]', Hello::class); -$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); -$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -``` - -In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the -route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that -class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what -arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router -if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple -callable we would need to manually use reflection as well to resolve all the arguments. - -But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) -which handles all of that for us. - -After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo -switch section in the Bootstrap.php file to use the container->call() method: - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2]; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$response = $container->call($handler, $args); -``` - -Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. - -But by now you should know that I do not like to depend on specific implementations and the call method is not defined in -the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container -or any other implementation. - -Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific -container implementation so we could use it with any container that implements the ContainerInterface. And best of all -the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that -we could implement if we ever want to write our own implementation or we could write an adapter that uses a different -class that solves the same problem. - -But for now we are using the solution provided by PHP-DI. -So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2] ?? []; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$invoker = $container->get(InvokerInterface::class); -assert($invoker instanceof InvokerInterface); -$response = $invoker->call($handler, $args); -assert($response instanceof ResponseInterface); -``` - -Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) -by php, and even some more. - -But let us move on to something more fun and add some templating functionality to our application as we are trying to build -a website in the end. - -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/implementation/15-adding-content/data/pages/11-templating.md b/implementation/15-adding-content/data/pages/11-templating.md deleted file mode 100644 index 3759664..0000000 --- a/implementation/15-adding-content/data/pages/11-templating.md +++ /dev/null @@ -1,240 +0,0 @@ -[<< previous](10-invoker.md) | [next >>](12-configuration.md) - -### Templating - -A template engine is not necessary with PHP because the language itself can take care of that. But it can make things -like escaping values easier. They also make it easier to draw a clear line between your application logic and the -template files which should only put your variables into the HTML code. - -A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please -also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. -Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. - -For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install -that package before you continue (`composer require mustache/mustache`). - -Another well known alternative would be [Twig](http://twig.sensiolabs.org/). - -Now please go and have a look at the source code of the -[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class -does not implement an interface. - -You could just type hint against the concrete class. But the problem with this approach is that you create tight -coupling. - -In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the -implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want -to add functionality to the engine. You can't do that without going back and changing all your code that is tightly -coupled. - -What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need -another implementation, you just implement that interface in your new class and inject the new class instead. - -Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). -This sounds a lot more complicated than it is, so just follow along. - -First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). -This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. -A class can implement multiple interfaces if necessary. - -So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a -new folder in your `src/` folder with the name `Template` where you can put all the template related things. - -In there create a new interface `Renderer.php` that looks like this: - -```php - $data - * @return string - */ - public function render(string $template, array $data = []) : string; -} -``` - -Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file -`MustacheRenderer.php` with the following content: - -```php -engine->render($template, $data); - } -} -``` - -As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple -and only fulfills the interface. - -Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know -which implementation he has to inject when you hint for the interface. Add this line: - -```php -[ - ... - \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) - ->constructor(new Mustache_Engine), -] -``` - -Now update the Hello.php class to require an implementation of our renderer interface -and use that to render a string using mustache syntax. - - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - Renderer $renderer, - string $name = 'Stranger', - ): ResponseInterface { - $body = $response->getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render( - 'Hello {{name}}, the time is {{now}}!', - $data, - ); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} -``` - -Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. -But what we want is template files, so let's go back and change that. - -To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the -`dependencies.php` file and add the following code: - -```php -[ - ... - Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), - Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), -] -``` - -We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. -Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have -to rename all the template files. - -To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the -dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader -in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object -we can then use it in the factory. - -In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: - -``` -

Hello World

-Hello {{ name }} -``` - -Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` - -Navigate to the hello page in your browser to make sure everything works. - -One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies -file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration -values. - -Lets create a 'Settings' class in our './src' Folder: - -```php -addDefinitions([ - Settings::class => fn () => require __DIR__ '/settings.php', - ResponseInterface::class => create(Response::class), - ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), - Renderer::class => fn (ME $me) => new Mustache($me), - MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), -]); - -return $builder->build(); -``` - - - -And as always, don't forget to commit your changes. - - -[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/implementation/15-adding-content/data/pages/12-configuration.md b/implementation/15-adding-content/data/pages/12-configuration.md deleted file mode 100644 index a44dfd5..0000000 --- a/implementation/15-adding-content/data/pages/12-configuration.md +++ /dev/null @@ -1,201 +0,0 @@ -[<< previous](11-templating.md) | [next >>](13-refactoring.md) - -### Configuration - -In the last chapter we added some more definitions to our dependencies.php in that definitions -we needed to pass quite a few configuration settings and filesystem strings to the constructors -of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. - -As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. - -As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. - -Lets create a 'Settings' class in our './src' Folder: - -```php -filePath; - } -} -``` - -If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files -and craft a settings object from them. - -As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another -factory for our Container because everyone should have a Friend :) - -```php -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn () => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} -``` - -For this to work we need to change our dependencies.php file to just return the array of definitions: -And here we can instantly use the Settings object to create our template engine. - -```php - fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), -]; -``` - -Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: -require __DIR__ . '/../vendor/autoload.php'; - -```php -... -error_reporting(E_ALL); - -$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); -$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); -... -``` - -Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. - -[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/implementation/15-adding-content/data/pages/13-refactoring.md b/implementation/15-adding-content/data/pages/13-refactoring.md deleted file mode 100644 index 067e168..0000000 --- a/implementation/15-adding-content/data/pages/13-refactoring.md +++ /dev/null @@ -1,377 +0,0 @@ -[<< previous](12-configuration.md) | [next >>](14-middleware.md) - -### Refactoring - -By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no -reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. -After all the bootstrap file should just set up the classes needed for the handling logic and execute them. - -At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. -As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the -method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non -static and extracted an interface. - -'./src/Http/Emitter.php' -```php -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - echo $response->getBody(); - } -} -``` -After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following -code in the Bootstrap.php to emit the response: - -```php -/** @var Emitter $emitter */ -$emitter = $container->get(Emitter::class); -$emitter->emit($response); -``` - -If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily -write an adapter that implements your emitter interface and wraps that more advanced emitter - -Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and -calling the routerhandler that in the passes the request to a function and gets the response. - -For this to steps to be seperated we are going to create two more classes: -1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object -2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the - requestobject, fetches the correct object from the container and calls it to create a response. - -Lets create the HandlerInterface first: - -```php -getAttribute($this->routeAttributeName, false); - assert($handler !== false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} - -``` - -We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. -The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler -as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in -the next chapter. - -```php -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} -``` - -Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. -```php -[ - '...', - Emitter::class => fn (BasicEmitter $e) => $e, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, -], -``` - -And then we can update our Bootstrap.php to fetch all the services and let them handle the request. - -```php -... -$routeMiddleWare = $container->get(MiddlewareInterface::class); -assert($routeMiddleWare instanceof MiddlewareInterface); -$handler = $container->get(RoutedRequestHandler::class); -assert($handler instanceof RequestHandlerInterface); -$emitter = $container->get(Emitter::class); -assert($emitter instanceof Emitter); - -$request = $container->get(ServerRequestInterface::class); -assert($request instanceof ServerRequestInterface); - -$response = $routeMiddleWare->process($request, $handler); -$emitter->emit($response); -``` -Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of -code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). - -So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. - -I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class -should require to function properly. - -* A RequestFactory - We want our Kernel to be able to build the request itself -* An Emitter - Without an Emitter we will not be able to send the response to the client -* RouteMiddleware - To decore the request with the correct handler for the requested route -* RequestHandler - To delegate the request to the correct funtion that creates the response - -As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface -to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our -interface: - -```php -factory::fromGlobals(); - } - - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} -``` - -For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: - -```php -routeMiddleware->process($request, $this->handler); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} - -``` - -We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines - -```php -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); -``` - -You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking -at the Whoops output and adding the needed definitions to the dependencies.php file. - -And as always, don't forget to commit your changes. - -[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/implementation/15-adding-content/data/pages/14-middleware.md b/implementation/15-adding-content/data/pages/14-middleware.md deleted file mode 100644 index e698327..0000000 --- a/implementation/15-adding-content/data/pages/14-middleware.md +++ /dev/null @@ -1,298 +0,0 @@ -[<< previous](12-refactoring.md) | [next >>](14-invoker.md) - -### Middleware - -In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain -a bit more about what this interface does and why it is used in many applications. - -The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets -passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all -the middlewars to the client/emitter. - -So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the -response after it gets created by our handlers. - -So lets take a look at the middleware and the requesthandler interfaces - -```php -interface MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; -} - -interface RequestHandlerInterface -{ - public function handle(ServerRequestInterface $request): ResponseInterface; -} -``` - -The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a -requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the -response. - -But the middleware could just ignore the handler and produce a response on its own as the interface just requires us -to produce a response. - -A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users -that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the -database. - -In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates -the request with an 'isAuthenticated' attribute. - -If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that -response is not already cached, than we let the handler create the response and store that in the cache for a few -seconds - -```php -interface CacheInterface -{ - public function get(string $key, callable $resolver, int $ttl): mixed; -} -``` - -The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one -defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses -the callable to produce the value and stores it for the time specified in ttl. - -so lets write our caching middleware: - -```php -final class CachingMiddleware implements MiddlewareInterface -{ - public function __construct(private CacheInterface $cache){} - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { - $key = $request->getUri()->getPath(); - return $this->cache->get($key, fn() => $handler->handle($request), 10); - } - return $handler->handle($request); - } -} -``` - -we can also modify the response after it has been created by our application, for example we could implement a gzip -middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser -know that our application is used to serve dank memes: - -```php -final class DankMemeMiddleware implements MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - $response = $handler->handle($request); - return $response->withAddedHeader('Meme', 'Dank'); - } -} -``` - -but for our application we are going to just add two external middlewares: - -* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. -* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware - -```bash -composer require middlewares/trailing-slash -composer require middlewares/whoops -``` - -The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the -application as well as the middleware stack. - -Our desired request -> response flow looks something like this: - - Client - | ^ - v | - Kernel - | ^ - v | - Whoops Middleware - | ^ - v | - TrailingSlash - | ^ - v | - Routing - | ^ - v | - ContainerResolver - | ^ - v | - Controller/Action - -As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every -middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. - -```php -interface Pipeline -{ - public function dispatch(ServerRequestInterface $request): ResponseInterface; -} -``` - -And our implementation looks something like this: - -```php - $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} -``` - -Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code -that should produce our response. - -In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument -and store that itself as the current tip. - -There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) - -Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline -Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline - -```php -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} -``` - -And configure the container to use the Factory to create the Pipeline: - -```php - ..., - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - ... -``` -And of course a new file called middlewares.php in our config folder: -```php -pipeline->dispatch($request); -} -``` - -Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our -pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a -dependency container, if you want you can do that as well. - -[<< previous](12-refactoring.md) | [next >>](14-invoker.md) diff --git a/implementation/15-adding-content/phpstan-baseline.neon b/implementation/15-adding-content/phpstan-baseline.neon deleted file mode 100644 index 61697a1..0000000 --- a/implementation/15-adding-content/phpstan-baseline.neon +++ /dev/null @@ -1,7 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" - count: 1 - path: src/Http/InvokerRoutedHandler.php - diff --git a/implementation/15-adding-content/phpstan.neon b/implementation/15-adding-content/phpstan.neon deleted file mode 100644 index 2eac45a..0000000 --- a/implementation/15-adding-content/phpstan.neon +++ /dev/null @@ -1,8 +0,0 @@ -includes: - - phpstan-baseline.neon - -parameters: - level: max - paths: - - src - - config \ No newline at end of file diff --git a/implementation/15-adding-content/public/css/spectre-exp.min.css b/implementation/15-adding-content/public/css/spectre-exp.min.css deleted file mode 100644 index d313774..0000000 --- a/implementation/15-adding-content/public/css/spectre-exp.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/implementation/15-adding-content/public/css/spectre-icons.min.css b/implementation/15-adding-content/public/css/spectre-icons.min.css deleted file mode 100644 index 0276f7b..0000000 --- a/implementation/15-adding-content/public/css/spectre-icons.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/implementation/15-adding-content/public/css/spectre.min.css b/implementation/15-adding-content/public/css/spectre.min.css deleted file mode 100644 index 0fe23d9..0000000 --- a/implementation/15-adding-content/public/css/spectre.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/implementation/15-adding-content/public/favicon.ico b/implementation/15-adding-content/public/favicon.ico deleted file mode 100644 index 09499b8b3b3201e0f50088e3ac42e167778d1153..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/15-adding-content/public/index.php b/implementation/15-adding-content/public/index.php deleted file mode 100644 index d93da3a..0000000 --- a/implementation/15-adding-content/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render('hello', $data); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/15-adding-content/src/Action/Other.php b/implementation/15-adding-content/src/Action/Other.php deleted file mode 100644 index da9ceaf..0000000 --- a/implementation/15-adding-content/src/Action/Other.php +++ /dev/null @@ -1,16 +0,0 @@ -parse('This *works* **too!**'); - $response->getBody()->write($html); - return $response->withStatus(200); - } -} diff --git a/implementation/15-adding-content/src/Action/Page.php b/implementation/15-adding-content/src/Action/Page.php deleted file mode 100644 index 6a3aad0..0000000 --- a/implementation/15-adding-content/src/Action/Page.php +++ /dev/null @@ -1,80 +0,0 @@ -getPages(), - fn (string $filename) => str_contains($filename, $page) - ) - )[0]; - $markdown = file_get_contents($page); - - // fix the next and previous buttons to work with our routing - $markdown = preg_replace('/\(\d\d-/m', '(', $markdown); - $markdown = str_replace('.md)', ')', $markdown); - - $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); - $data = [ - 'title' => substr($page, 3), - 'content' => $this->parser->parse($markdown), - ]; - $html = $this->renderer->render('page/show', $data); - $this->response->getBody()->write($html); - return $this->response; - } - - public function list(): ResponseInterface - { - $pages = array_map(function (string $page) { - $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); - return [ - 'id' => substr($page, 0, 2), - 'title' => substr($page, 3), - ]; - }, $this->getPages()); - $html = $this->renderer->render('page/list', ['pages' => $pages]); - $this->response->getBody()->write($html); - return $this->response; - } - - /** - * @return string[] - */ - private function getPages(): array - { - $files = glob($this->pagesPath . '*.md'); - if ($files === false) { - throw new InternalServerError('cannot read pages'); - } - return $files; - } -} diff --git a/implementation/15-adding-content/src/Bootstrap.php b/implementation/15-adding-content/src/Bootstrap.php deleted file mode 100644 index 3abc2e5..0000000 --- a/implementation/15-adding-content/src/Bootstrap.php +++ /dev/null @@ -1,40 +0,0 @@ -getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); diff --git a/implementation/15-adding-content/src/Exception/InternalServerError.php b/implementation/15-adding-content/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/implementation/15-adding-content/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ -factory::fromGlobals(); - } - - /** - * @param UriInterface|string $uri - * @param array $serverParams - */ - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} diff --git a/implementation/15-adding-content/src/Factory/DoctrineEm.php b/implementation/15-adding-content/src/Factory/DoctrineEm.php deleted file mode 100644 index b0be39b..0000000 --- a/implementation/15-adding-content/src/Factory/DoctrineEm.php +++ /dev/null @@ -1,32 +0,0 @@ -settings->doctrine['devMode']); - - $config->setMetadataDriverImpl( - new AttributeDriver( - $this->settings->doctrine['metadataDirs'] - ) - ); - - return EntityManager::create( - $this->settings->connection, - $config, - ); - } -} diff --git a/implementation/15-adding-content/src/Factory/FileSystemSettingsProvider.php b/implementation/15-adding-content/src/Factory/FileSystemSettingsProvider.php deleted file mode 100644 index f071078..0000000 --- a/implementation/15-adding-content/src/Factory/FileSystemSettingsProvider.php +++ /dev/null @@ -1,22 +0,0 @@ -filePath; - assert($settings instanceof Settings); - return $settings; - } -} diff --git a/implementation/15-adding-content/src/Factory/PipelineProvider.php b/implementation/15-adding-content/src/Factory/PipelineProvider.php deleted file mode 100644 index 77738f8..0000000 --- a/implementation/15-adding-content/src/Factory/PipelineProvider.php +++ /dev/null @@ -1,25 +0,0 @@ -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} diff --git a/implementation/15-adding-content/src/Factory/RequestFactory.php b/implementation/15-adding-content/src/Factory/RequestFactory.php deleted file mode 100644 index 2b17abc..0000000 --- a/implementation/15-adding-content/src/Factory/RequestFactory.php +++ /dev/null @@ -1,11 +0,0 @@ -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = $settings; - $builder->addDefinitions($dependencies); - // $builder->enableCompilation('/tmp'); - return $builder->build(); - } -} diff --git a/implementation/15-adding-content/src/Factory/SettingsProvider.php b/implementation/15-adding-content/src/Factory/SettingsProvider.php deleted file mode 100644 index ce1c5f0..0000000 --- a/implementation/15-adding-content/src/Factory/SettingsProvider.php +++ /dev/null @@ -1,10 +0,0 @@ -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - - echo $response->getBody(); - } -} diff --git a/implementation/15-adding-content/src/Http/ContainerPipeline.php b/implementation/15-adding-content/src/Http/ContainerPipeline.php deleted file mode 100644 index 816cedd..0000000 --- a/implementation/15-adding-content/src/Http/ContainerPipeline.php +++ /dev/null @@ -1,82 +0,0 @@ - $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} diff --git a/implementation/15-adding-content/src/Http/Emitter.php b/implementation/15-adding-content/src/Http/Emitter.php deleted file mode 100644 index ce4c035..0000000 --- a/implementation/15-adding-content/src/Http/Emitter.php +++ /dev/null @@ -1,10 +0,0 @@ -getAttribute($this->routeAttributeName, false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} diff --git a/implementation/15-adding-content/src/Http/Pipeline.php b/implementation/15-adding-content/src/Http/Pipeline.php deleted file mode 100644 index 1a9dcda..0000000 --- a/implementation/15-adding-content/src/Http/Pipeline.php +++ /dev/null @@ -1,11 +0,0 @@ -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} diff --git a/implementation/15-adding-content/src/Http/RoutedRequestHandler.php b/implementation/15-adding-content/src/Http/RoutedRequestHandler.php deleted file mode 100644 index a7407c9..0000000 --- a/implementation/15-adding-content/src/Http/RoutedRequestHandler.php +++ /dev/null @@ -1,10 +0,0 @@ -pipeline->dispatch($request); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} diff --git a/implementation/15-adding-content/src/Middleware/CacheMiddleware.php b/implementation/15-adding-content/src/Middleware/CacheMiddleware.php deleted file mode 100644 index 1bd58c5..0000000 --- a/implementation/15-adding-content/src/Middleware/CacheMiddleware.php +++ /dev/null @@ -1,40 +0,0 @@ -getMethod() === 'GET' && ! $this->settings->isDev()) { - $key = (string) $request->getUri(); - $key = base64_encode($key); - $callback = fn () => $handler->handle($request); - $cached = $this->cache->get($key, function (ItemInterface $item) use ($callback) { - $item->expiresAfter(120); - $response = $callback(); - return $this->serializer::toString($response); - }); - return $this->serializer::fromString($cached); - } - return $handler->handle($request); - } -} diff --git a/implementation/15-adding-content/src/Model/MarkdownPage.php b/implementation/15-adding-content/src/Model/MarkdownPage.php deleted file mode 100644 index bae383c..0000000 --- a/implementation/15-adding-content/src/Model/MarkdownPage.php +++ /dev/null @@ -1,21 +0,0 @@ - $this->repo->all(); - if ($this->settings->isDev()) { - return $callback(); - } - return $this->cache->get('ALLPAGES', function (ItemInterface $item) use ($callback) { - $item->expiresAfter(30); - return $callback(); - }); - } - - public function byId(int $id): MarkdownPage - { - $callback = fn () => $this->repo->byId($id); - if ($this->settings->isDev()) { - return $callback(); - } - return $this->cache->get('PAGE' . $id, function (ItemInterface $item) use ($callback) { - $item->expiresAfter(30); - return $callback(); - }); - } - - public function byTitle(string $title): MarkdownPage - { - $callback = fn () => $this->repo->byTitle($title); - if ($this->settings->isDev()) { - return $callback(); - } - return $this->cache->get('PAGE' . $title, function (ItemInterface $item) use ($callback) { - $item->expiresAfter(30); - return $callback(); - }); - } - - public function save(MarkdownPage $page): MarkdownPage - { - return $this->repo->save($page); - } -} diff --git a/implementation/15-adding-content/src/Repository/DoctrineMarkdownPageRepo.php b/implementation/15-adding-content/src/Repository/DoctrineMarkdownPageRepo.php deleted file mode 100644 index 8d1d457..0000000 --- a/implementation/15-adding-content/src/Repository/DoctrineMarkdownPageRepo.php +++ /dev/null @@ -1,59 +0,0 @@ - */ - private EntityRepository $repo; - - public function __construct( - private EntityManagerInterface $entityManager - ) { - $this->repo = $this->entityManager->getRepository(MarkdownPage::class); - } - - /** - * @inheritDoc - */ - public function all(): array - { - usleep(random_int(500, 1500) * 1000); - return $this->repo->findAll(); - } - - public function byId(int $id): MarkdownPage - { - usleep(random_int(500, 1500) * 1000); - $page = $this->repo->findOneBy(['id' => $id]); - if (! $page instanceof MarkdownPage) { - throw new NotFound; - } - return $page; - } - - public function byTitle(string $title): MarkdownPage - { - usleep(random_int(500, 1500) * 1000); - $page = $this->repo->findOneBy(['title' => $title]); - if (! $page instanceof MarkdownPage) { - throw new NotFound; - } - return $page; - } - - public function save(MarkdownPage $page): MarkdownPage - { - $this->entityManager->persist($page); - $this->entityManager->flush(); - return $page; - } -} diff --git a/implementation/15-adding-content/src/Repository/MarkdownPageFilesystem.php b/implementation/15-adding-content/src/Repository/MarkdownPageFilesystem.php deleted file mode 100644 index 25dfd97..0000000 --- a/implementation/15-adding-content/src/Repository/MarkdownPageFilesystem.php +++ /dev/null @@ -1,69 +0,0 @@ -dataPath . '*.md'); - assert(is_array($fileNames)); - return array_map(function (string $name): MarkdownPage { - usleep(random_int(200, 500) * 1000); - $content = file_get_contents($name); - $name = str_replace($this->dataPath, '', $name); - $name = str_replace('.md', '', $name); - $id = (int) substr($name, 0, 2); - $title = substr($name, 3); - return new MarkdownPage($id, $title, $content); - }, $fileNames); - } - - public function byId(int $id): MarkdownPage - { - $callback = fn (MarkdownPage $p): bool => $p->id === $id; - $filtered = array_values(array_filter($this->all(), $callback)); - if (count($filtered) === 0) { - throw new NotFound; - } - return $filtered[0]; - } - - public function byTitle(string $title): MarkdownPage - { - $callback = fn (MarkdownPage $p): bool => $p->title === $title; - $filtered = array_values(array_filter($this->all(), $callback)); - if (count($filtered) === 0) { - throw new NotFound; - } - return $filtered[0]; - } - - public function save(MarkdownPage $page): MarkdownPage - { - return $page; - } -} diff --git a/implementation/15-adding-content/src/Repository/MarkdownPageRepo.php b/implementation/15-adding-content/src/Repository/MarkdownPageRepo.php deleted file mode 100644 index 3f80899..0000000 --- a/implementation/15-adding-content/src/Repository/MarkdownPageRepo.php +++ /dev/null @@ -1,19 +0,0 @@ -environment === 'dev'; - } -} diff --git a/implementation/15-adding-content/src/Template/GithubMarkdownRenderer.php b/implementation/15-adding-content/src/Template/GithubMarkdownRenderer.php deleted file mode 100644 index 121504b..0000000 --- a/implementation/15-adding-content/src/Template/GithubMarkdownRenderer.php +++ /dev/null @@ -1,28 +0,0 @@ -addExtension($commonMarkCoreExtension); - $environment->addExtension($githubFlavoredMarkdownExtension); - $this->engine = new MarkdownConverter($environment); - } - - public function render(string $markdown): string - { - return (string) $this->engine->convert($markdown); - } -} diff --git a/implementation/15-adding-content/src/Template/MarkdownParser.php b/implementation/15-adding-content/src/Template/MarkdownParser.php deleted file mode 100644 index d404005..0000000 --- a/implementation/15-adding-content/src/Template/MarkdownParser.php +++ /dev/null @@ -1,8 +0,0 @@ -engine->render($template, $data); - } -} diff --git a/implementation/15-adding-content/src/Template/ParsedownParser.php b/implementation/15-adding-content/src/Template/ParsedownParser.php deleted file mode 100644 index 2ffd287..0000000 --- a/implementation/15-adding-content/src/Template/ParsedownParser.php +++ /dev/null @@ -1,17 +0,0 @@ -parser->parse($markdown); - } -} diff --git a/implementation/15-adding-content/src/Template/Renderer.php b/implementation/15-adding-content/src/Template/Renderer.php deleted file mode 100644 index ff916ed..0000000 --- a/implementation/15-adding-content/src/Template/Renderer.php +++ /dev/null @@ -1,11 +0,0 @@ - $data - */ - public function render(string $template, array $data = []): string; -} diff --git a/implementation/15-adding-content/templates/hello.html b/implementation/15-adding-content/templates/hello.html deleted file mode 100644 index 15a4cd2..0000000 --- a/implementation/15-adding-content/templates/hello.html +++ /dev/null @@ -1,6 +0,0 @@ -{{> partials/head }} -
-

Hello {{name}}

-

The time is {{now}}

-
-{{> partials/foot }} diff --git a/implementation/15-adding-content/templates/page.html b/implementation/15-adding-content/templates/page.html deleted file mode 100644 index c3c5284..0000000 --- a/implementation/15-adding-content/templates/page.html +++ /dev/null @@ -1,5 +0,0 @@ -{{> partials/head }} -
- {{{content}}} -
-{{> partials/foot }} diff --git a/implementation/15-adding-content/templates/page/list.html b/implementation/15-adding-content/templates/page/list.html deleted file mode 100644 index bf42348..0000000 --- a/implementation/15-adding-content/templates/page/list.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Pages - - - -
- -
- - \ No newline at end of file diff --git a/implementation/15-adding-content/templates/page/show.html b/implementation/15-adding-content/templates/page/show.html deleted file mode 100644 index abe295e..0000000 --- a/implementation/15-adding-content/templates/page/show.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - {{title}} - - - - - - -
- {{{content}}} -
- - \ No newline at end of file diff --git a/implementation/15-adding-content/templates/pagelist.html b/implementation/15-adding-content/templates/pagelist.html deleted file mode 100644 index 538e2c4..0000000 --- a/implementation/15-adding-content/templates/pagelist.html +++ /dev/null @@ -1,11 +0,0 @@ -{{> partials/head }} -
- -
-{{> partials/foot }} diff --git a/implementation/15-adding-content/templates/partials/foot.html b/implementation/15-adding-content/templates/partials/foot.html deleted file mode 100644 index 17c7245..0000000 --- a/implementation/15-adding-content/templates/partials/foot.html +++ /dev/null @@ -1,3 +0,0 @@ -
- - \ No newline at end of file diff --git a/implementation/15-adding-content/templates/partials/head.html b/implementation/15-adding-content/templates/partials/head.html deleted file mode 100644 index 421d387..0000000 --- a/implementation/15-adding-content/templates/partials/head.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - No Framework: {{title}} - - - - - -
diff --git a/implementation/16-caching/.php-cs-fixer.php b/implementation/16-caching/.php-cs-fixer.php deleted file mode 100644 index 705a7d7..0000000 --- a/implementation/16-caching/.php-cs-fixer.php +++ /dev/null @@ -1,38 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config' - ]) - ); \ No newline at end of file diff --git a/implementation/16-caching/.phpcs.xml.dist b/implementation/16-caching/.phpcs.xml.dist deleted file mode 100644 index 3b433f6..0000000 --- a/implementation/16-caching/.phpcs.xml.dist +++ /dev/null @@ -1,9 +0,0 @@ - - - - - src - config - - - \ No newline at end of file diff --git a/implementation/16-caching/composer.json b/implementation/16-caching/composer.json deleted file mode 100644 index e85cc50..0000000 --- a/implementation/16-caching/composer.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3", - "mustache/mustache": "^2.14", - "psr/http-server-middleware": "^1.0", - "middlewares/trailing-slash": "^2.0", - "middlewares/whoops": "^2.0", - "erusev/parsedown": "^1.7", - "symfony/cache": "^6.0" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require-dev": { - "phpstan/phpstan": "^1.5", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0", - "squizlabs/php_codesniffer": "^3.6", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1", - "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0" - }, - "config": { - "allow-plugins": { - "phpstan/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "serve": [ - "Composer\\Config::disableProcessTimeout", - "php -S 0.0.0.0:1234 -t public" - ], - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" - } -} diff --git a/implementation/16-caching/composer.lock b/implementation/16-caching/composer.lock deleted file mode 100644 index 0c626d9..0000000 --- a/implementation/16-caching/composer.lock +++ /dev/null @@ -1,2273 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "5286ff6a5dbbe21ace2a7359b49b8780", - "packages": [ - { - "name": "erusev/parsedown", - "version": "1.7.4", - "source": { - "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35" - }, - "type": "library", - "autoload": { - "psr-0": { - "Parsedown": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com" - } - ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org", - "keywords": [ - "markdown", - "parser" - ], - "support": { - "issues": "https://github.com/erusev/parsedown/issues", - "source": "https://github.com/erusev/parsedown/tree/1.7.x" - }, - "time": "2019-12-30T22:54:17+00:00" - }, - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.9.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2022-03-29T20:12:16+00:00" - }, - { - "name": "middlewares/trailing-slash", - "version": "v2.0.1", - "source": { - "type": "git", - "url": "https://github.com/middlewares/trailing-slash.git", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", - "shasum": "" - }, - "require": { - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to normalize the trailing slash of the uri path", - "homepage": "https://github.com/middlewares/trailing-slash", - "keywords": [ - "http", - "middleware", - "normalize", - "path", - "psr-15", - "psr-7", - "slash" - ], - "support": { - "issues": "https://github.com/middlewares/trailing-slash/issues", - "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" - }, - "time": "2020-12-02T00:06:55+00:00" - }, - { - "name": "middlewares/utils", - "version": "v3.3.0", - "source": { - "type": "git", - "url": "https://github.com/middlewares/utils.git", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^v2.16", - "guzzlehttp/psr7": "^2.0", - "laminas/laminas-diactoros": "^2.4", - "nyholm/psr7": "^1.0", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "slim/psr7": "^1.4", - "squizlabs/php_codesniffer": "^3.5", - "sunrise/http-message": "^1.0", - "sunrise/http-server-request": "^1.0", - "sunrise/stream": "^1.0.15", - "sunrise/uri": "^1.0.15" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\Utils\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Common utils for PSR-15 middleware packages", - "homepage": "https://github.com/middlewares/utils", - "keywords": [ - "PSR-11", - "http", - "middleware", - "psr-15", - "psr-17", - "psr-7" - ], - "support": { - "issues": "https://github.com/middlewares/utils/issues", - "source": "https://github.com/middlewares/utils/tree/v3.3.0" - }, - "time": "2021-07-04T17:56:23+00:00" - }, - { - "name": "middlewares/whoops", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/middlewares/whoops.git", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", - "shasum": "" - }, - "require": { - "filp/whoops": "^2.5", - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "eloquent/phony-phpunit": "^5.0 || ^7.0", - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to use Whoops as error handler", - "homepage": "https://github.com/middlewares/whoops", - "keywords": [ - "error", - "http", - "middleware", - "psr-15", - "psr-7", - "server", - "whoops" - ], - "support": { - "issues": "https://github.com/middlewares/whoops/issues", - "source": "https://github.com/middlewares/whoops/tree/v2.0.2" - }, - "time": "2022-01-27T20:31:30+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/cache", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" - }, - "time": "2021-02-03T23:26:27+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/http-server-middleware", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-middleware.git", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0", - "psr/http-server-handler": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side middleware", - "keywords": [ - "http", - "http-interop", - "middleware", - "psr", - "psr-15", - "psr-7", - "request", - "response" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-middleware/issues", - "source": "https://github.com/php-fig/http-server-middleware/tree/master" - }, - "time": "2018-10-30T17:12:04+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - }, - { - "name": "symfony/cache", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache.git", - "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", - "reference": "57faad4e0d694f9961f517fdd5e6fbb1f6d0e04f", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "psr/cache": "^2.0|^3.0", - "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^1.1.7|^2|^3", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/var-exporter": "^5.4|^6.0" - }, - "conflict": { - "doctrine/dbal": "<2.13.1", - "symfony/dependency-injection": "<5.4", - "symfony/http-kernel": "<5.4", - "symfony/var-dumper": "<5.4" - }, - "provide": { - "psr/cache-implementation": "2.0|3.0", - "psr/simple-cache-implementation": "1.0|2.0|3.0", - "symfony/cache-implementation": "1.1|2.0|3.0" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/dbal": "^2.13.1|^3.0", - "predis/predis": "^1.1", - "psr/simple-cache": "^1.0|^2.0|^3.0", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/filesystem": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/messenger": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Cache\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", - "homepage": "https://symfony.com", - "keywords": [ - "caching", - "psr6" - ], - "support": { - "source": "https://github.com/symfony/cache/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "symfony/cache-contracts", - "version": "v3.0.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache-contracts.git", - "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/2f7463f156cf9c665d9317e21a809c3bbff5754e", - "reference": "2f7463f156cf9c665d9317e21a809c3bbff5754e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "psr/cache": "^3.0" - }, - "suggest": { - "symfony/cache-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Cache\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to caching", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.0.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-08-17T15:35:52+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v2.5.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8", - "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-07-12T14:48:14+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v2.5.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", - "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "suggest": { - "symfony/service-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-04T16:48:04+00:00" - }, - { - "name": "symfony/var-exporter", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-exporter.git", - "reference": "130229a482abf17635a685590958894dfb4b4360" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/130229a482abf17635a685590958894dfb4b4360", - "reference": "130229a482abf17635a685590958894dfb4b4360", - "shasum": "" - }, - "require": { - "php": ">=8.0.2" - }, - "require-dev": { - "symfony/var-dumper": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\VarExporter\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows exporting any serializable PHP data structure to plain PHP code", - "homepage": "https://symfony.com", - "keywords": [ - "clone", - "construct", - "export", - "hydrate", - "instantiate", - "serialize" - ], - "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "doctrine/coding-standard", - "version": "8.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/coding-standard.git", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "slevomat/coding-standard": "^6.4.1", - "squizlabs/php_codesniffer": "^3.5.8" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Steve Müller", - "email": "st.mueller@dzh-online.de" - } - ], - "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", - "keywords": [ - "checks", - "code", - "coding", - "cs", - "doctrine", - "rules", - "sniffer", - "sniffs", - "standard", - "style" - ], - "support": { - "issues": "https://github.com/doctrine/coding-standard/issues", - "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" - }, - "time": "2021-04-03T10:54:55+00:00" - }, - { - "name": "mnapoli/hard-mode", - "version": "0.3.0", - "source": { - "type": "git", - "url": "https://github.com/mnapoli/hard-mode.git", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", - "shasum": "" - }, - "require": { - "doctrine/coding-standard": "^8.0" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Hard mode for PHP", - "support": { - "issues": "https://github.com/mnapoli/hard-mode/issues", - "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" - }, - "time": "2020-10-12T07:54:37+00:00" - }, - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.9", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.26", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/master" - }, - "time": "2020-08-03T20:32:43+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.5.3", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/39953ac1452a8843702ee41a35b4861d3e8207a7", - "reference": "39953ac1452a8843702ee41a35b4861d3e8207a7", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.3" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-03-30T21:55:08+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^1.2.0" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" - }, - "time": "2021-11-18T09:30:29+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "6.4.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpstan/phpstan": "0.12.48", - "phpstan/phpstan-deprecation-rules": "0.12.5", - "phpstan/phpstan-phpunit": "0.12.16", - "phpstan/phpstan-strict-rules": "0.12.5", - "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2020-10-05T12:39:37+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.3.0" -} diff --git a/implementation/16-caching/config/dependencies.php b/implementation/16-caching/config/dependencies.php deleted file mode 100644 index 376aea0..0000000 --- a/implementation/16-caching/config/dependencies.php +++ /dev/null @@ -1,54 +0,0 @@ - fn (SystemClockNow $n) => $n, - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, - Emitter::class => fn (BasicEmitter $e) => $e, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, - CacheInterface::class => fn (FilesystemAdapter $a) => $a, - MarkdownPageRepo::class => fn (MarkdownPageFilesystem $r) => $r, - - // Factories - ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), - Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - MarkdownPageFilesystem::class => fn (Settings $s) => new MarkdownPageFilesystem($s->pagesPath), - CachedMarkdownPageRepo::class => fn (CacheInterface $c, MarkdownPageFilesystem $r) => new CachedMarkdownPageRepo($c, $r), -]; diff --git a/implementation/16-caching/config/middlewares.php b/implementation/16-caching/config/middlewares.php deleted file mode 100644 index 459547e..0000000 --- a/implementation/16-caching/config/middlewares.php +++ /dev/null @@ -1,13 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/page/{page}', Page::class); - $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); - $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -}; diff --git a/implementation/16-caching/config/settings.php b/implementation/16-caching/config/settings.php deleted file mode 100644 index 8a0861d..0000000 --- a/implementation/16-caching/config/settings.php +++ /dev/null @@ -1,12 +0,0 @@ ->](02-composer.md) - -### Front Controller - -A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. - -To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. - -A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. - -The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. - -But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. - -So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. - -Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: - -```php ->](02-composer.md) diff --git a/implementation/16-caching/data/pages/02-composer.md b/implementation/16-caching/data/pages/02-composer.md deleted file mode 100644 index a25a4a8..0000000 --- a/implementation/16-caching/data/pages/02-composer.md +++ /dev/null @@ -1,75 +0,0 @@ -[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) - -### Composer - -[Composer](https://getcomposer.org/) is a dependency manager for PHP. - -Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do -something. With Composer, you can install third-party libraries for your application. - -If you don't have Composer installed already, head over to the website and install it. You can find Composer packages -for your project on [Packagist](https://packagist.org/). - -Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will -be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. - -Add the following content to the file: - -```json -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubiana", - "email": "lubiana@hannover.ccc.de" - } - ] -} -``` - -In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use -whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. -Just replace it with your namespace in your own code. - -I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. - -As the Bootstrap.php file is placed in that directory we should -add the namespace to the File as well. Here is my current Bootstrap.php -as a reference: - -```php ->](03-error-handler.md) diff --git a/implementation/16-caching/data/pages/03-error-handler.md b/implementation/16-caching/data/pages/03-error-handler.md deleted file mode 100644 index 60465d0..0000000 --- a/implementation/16-caching/data/pages/03-error-handler.md +++ /dev/null @@ -1,79 +0,0 @@ -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) - -### Error Handler - -An error handler allows you to customize what happens if your code results in an error. - -A nice error page with a lot of information for debugging goes a long way during development. So the first package -for your application will take care of that. - -I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. -If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, -you have total control over your project. - -An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) - -To install a new package, open up your `composer.json` and add the package to the require part. It should now look -like this: - -```php -"require": { - "php": ">=8.1.0", - "filp/whoops": "^2.14" -}, -``` - -Now run `composer update` in your console and it will be installed. - -Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, -i that case composer automatically installs the package and updates your composer.json-file. - -But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, -ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you -only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. - -**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message -can help someone to gain access to your system. Always show a user friendly error page instead and send an email to -yourself, write to a log or something similar. So only you can see the errors in the production environment. - -For development that does not make sense though -- you want a nice error page. The solution is to have an environment -switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in -case no environment has been set. - -Then after the error handler registration, throw an `Exception` to test if everything is working correctly. -Your `Bootstrap.php` should now look similar to this: - -```php -pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (\Throwable $e) { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -throw new \Exception("Ooooopsie"); - -``` - -You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until -you get it working. Now would also be a good time for another commit. - - -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/implementation/16-caching/data/pages/04-development-helpers.md b/implementation/16-caching/data/pages/04-development-helpers.md deleted file mode 100644 index 74f913c..0000000 --- a/implementation/16-caching/data/pages/04-development-helpers.md +++ /dev/null @@ -1,260 +0,0 @@ -[<< previous](03-error-handler.md) | [next >>](05-http.md) - -### Development Helpers - -I have added some more helpers to my composer.json that help me with development. As these are scripts and programms -used only for development they should not be used in a production environment. Composer has a specific sections in its -file called "dev-dependencies", everything that is required in this section does not get installen in production. - -Let's install our dev-helpers and i will explain them one by one: -`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` - -#### Static Code Analysis with phpstan - -Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or -create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements -that are not (or not always) available, if-statements that always are true and a lot of other stuff. - -A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. - -```php -/** - * @param \DateTime $date - * @return void - */ -function printDate($date) { - $date->format('Y-m-d H:i:s'); -} - -printDate('now'); -``` -if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` - -It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has -no use, and secondly, that we are calling the function with a string instead of a datetime object. - -```shell -1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - ------ --------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ --------------------------------------------------------------------------------------------- -30 Call to method DateTime::format() on a separate line has no effect. -33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. - ------ --------------------------------------------------------------------------------------------- -``` - -The second error is something that "declare strict-types" already catches for us, but the first error is something that -we usually would not discover easily without speccially looking for this errortype. - -We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and -path everytime we want to check our code for errors: - -```yaml -parameters: - level: max - paths: - - src -``` -now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project - -With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us -some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. - -```shell -composer require --dev phpstan/extension-installer -composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules -``` - -During the first install you need to allow the extension installer to actually install the extension. The second command -installs some more strict rulesets and activates them in phpstan. - -If we now rerun phpstan it already tells us about some errors we have made: - -``` - ------ ----------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ ----------------------------------------------------------------------------------------------- -10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider - using long ternary. -25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: - http://bit.ly/subtypeexception -26 Unreachable statement - code above always terminates. - ------ ----------------------------------------------------------------------------------------------- -``` - -The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove -that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific -problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working -on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages -everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we -are adding to our code. - -In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the -phpstan.neon file and run phpstan with the -'--generate-baseline' option: - -```yaml -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` -```shell -[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline -Note: Using configuration file /home/vagrant/app/phpstan.neon. - 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - - - [OK] Baseline generated with 1 error. - - -``` - -you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) - -#### PHP-CS-Fixer - -Another great tool is the php-cs-fixer, which just applies a specific style to your code. - -when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current -directory. - -You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) - -personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup -a configuration file that i use in all my projects .php-cs-fixer.php: - -```php -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - ]) - ); -``` - -#### PHP Codesniffer - -The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra -rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. - -it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. - -Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have -guessed we are adding some extra rules. - -Lets install the ruleset with composer -```shell -composer require --dev mnapoli/hard-mode -``` - -and add a configuration file to actually use it '.phpcs.xml.dist' -```xml - - - - - src - - - -``` - -running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. - -``` -[vagrant@archlinux app]$ ./vendor/bin/phpcs - -FILE: src/Bootstrap.php ----------------------------------------------------------------------------------------------------- -FOUND 4 ERRORS AFFECTING 4 LINES ----------------------------------------------------------------------------------------------------- - 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. - 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead ----------------------------------------------------------------------------------------------------- -PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY ----------------------------------------------------------------------------------------------------- - -Time: 639ms; Memory: 10MB -``` - -You can then use `./vendor/bin/phpcbf` to try to fix them - - -#### Symfony Var-Dumper - -another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small -functions. - -dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects -or arrays. - -dd() on the other hand is a function that dumps its parameters and then exits the php-script. - -you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. - -#### Composer scripts - -now we have a few commands that are available on the command line. i personally do not like to type complex commands -with lots of parameters by hand all the time, so i added a few lines to my composer.json: - -```json -"scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" -}, -``` - -that way i can just type "composer" followed by the command name in the root of my project. if i want to start the -php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the -time. - -You could also configure PhpStorm to automatically run these commands in the background and highlight the violations -directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my -flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. - -My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before -commiting and pushing the code. - -[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/implementation/16-caching/data/pages/05-http.md b/implementation/16-caching/data/pages/05-http.md deleted file mode 100644 index 6166214..0000000 --- a/implementation/16-caching/data/pages/05-http.md +++ /dev/null @@ -1,124 +0,0 @@ -[<< previous](04-development-helpers.md) | [next >>](06-router.md) - -### HTTP - -PHP already has a few things built in to make working with HTTP easier. For example there are the -[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. - -These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, -if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, -then you will want a class with a nice object-oriented interface that you can use in your application instead. - -Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The -standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php -projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. - -As this is a widely adopted standard there are already several implementations available for us to use. I will choose -the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. - -Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a -[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. - -Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use -that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding -the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). - - -to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit -enter - -Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): - -```php -$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); -$response = new \Laminas\Diactoros\Response; -$response->getBody()->write('Hello World! '); -$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); -``` - -This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. - -In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the -write()-Method on that object. - - -To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: - -```php -echo $response->getBody(); -``` - -This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only -stores data. - -You can play around with the other methods of the Request object and take a look at its content with the dd() function. - -```php -dd($response) -``` - -Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot -be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which -creates a copy of the request object with the changed property and returns that clone: - -```php -$response = $response->withStatus(200); -$response = $response->withAddedHeader('Content-type', 'application/json'); -``` - -If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been -defined this way. - -But if you have been keeping attention you might argue that the following line should not work if the request object is -immutable. - -```php -$response->getBody()->write('Hello World!'); -``` - -The response-body implements a stream interface which is immutable for some reasons that are described in the -[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be -aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in -the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. -I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content -to a response object. - -```php -$body = $response->getBody(); -$body->write('Hello World!'); -$response = $response->withBody($body); -``` - -Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our -output-logic a little bit more. Replace the line that echos the response-body with the following: - -```php -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); -``` - -This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a -webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. - -Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. - -Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter - - -[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/implementation/16-caching/data/pages/06-router.md b/implementation/16-caching/data/pages/06-router.md deleted file mode 100644 index 6c39ae5..0000000 --- a/implementation/16-caching/data/pages/06-router.md +++ /dev/null @@ -1,101 +0,0 @@ -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) - -### Router - -A router dispatches to different handlers depending on rules that you have set up. - -With your current setup it does not matter what URL is used to access the application, it will always result in the same -response. So let's fix that now. - -I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own -favorite package. - -Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) - -By now you know how to install Composer packages, so I will leave that to you. - -Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. - -```php -$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -switch ($routeInfo[0]) { - case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: - $response = (new \Laminas\Diactoros\Response)->withStatus(405); - $response->getBody()->write('Method not allowed'); - $response = $response->withStatus(405); - break; - case \FastRoute\Dispatcher::FOUND: - $handler = $routeInfo[1]; - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - /** @var \Psr\Http\Message\ResponseInterface $response */ - $response = call_user_func($handler, $request); - break; - case \FastRoute\Dispatcher::NOT_FOUND: - default: - $response = (new \Laminas\Diactoros\Response)->withStatus(404); - $response->getBody()->write('Not Found!'); - break; -} -``` - -In the first part of the code, you are registering the available routes for your application. In the second part, the -dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, -we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. -If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. - -This setup might work for really small applications, but once you start adding a few routes your bootstrap file will -quickly get cluttered. So let's move them out into a separate file. - -Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; - -```php -addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}; -``` - -Now let's rewrite the route dispatcher part to use the `Routes.php` file. - -```php -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); -``` - -This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. - -Of course we now need to add the 'config' folder to the configuration files of our -devhelpers so that they can scan that directory as well. - -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/implementation/16-caching/data/pages/07-dispatching-to-a-class.md b/implementation/16-caching/data/pages/07-dispatching-to-a-class.md deleted file mode 100644 index 0c961a4..0000000 --- a/implementation/16-caching/data/pages/07-dispatching-to-a-class.md +++ /dev/null @@ -1,137 +0,0 @@ -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) - -### Dispatching to a Class - -In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). -MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to -learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) -and the followup posts. - -So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). - -We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other -common names are 'Controllers' or 'Actions'. - -Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. -In there, create a `Hello.php` file. - -```php -getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - } -} -``` - -You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) -that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is -fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement -it in some other parts of our application as well. In order to use that Interface we have to require it with composer: -'composer require psr/http-server-handler'. - -The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. -At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. - -Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: - -```php -return function(\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); - $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); -}; -``` - -Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared -the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing -the response to the one we defined for the second route. - -To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: - -```php -case \FastRoute\Dispatcher::FOUND: - $handler = new $routeInfo[1]; - if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { - throw new \Exception('Invalid Requesthandler'); - } - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - assert($response instanceof \Psr\Http\Message\ResponseInterface) - break; -``` - -So instead of just calling a method you are now instantiating an object and then calling the method on it. - -Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. - -And of course don't forget to commit your changes. - -Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still -generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for -example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still -have our whoopsie error-handler but i like to be more explicit in my control flow. - -In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new -Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and -InternalServerError. All three should extend phps Base Exception class. - -Here is my NotFound.php for example. - -```php - $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - break; - case Dispatcher::METHOD_NOT_ALLOWED: - throw new MethodNotAllowed; - - case Dispatcher::NOT_FOUND: - default: - throw new NotFound; - } -} catch (MethodNotAllowed) { - $response = (new Response)->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = (new Response)->withStatus(404); - $response->getBody()->write('Not Found'); -} catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); -} -``` - -Check if our code still works, try to trigger some errors, run phpstan and the fix command -and don't forget to commit your changes. - -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/implementation/16-caching/data/pages/08-inversion-of-control.md b/implementation/16-caching/data/pages/08-inversion-of-control.md deleted file mode 100644 index 21f4f23..0000000 --- a/implementation/16-caching/data/pages/08-inversion-of-control.md +++ /dev/null @@ -1,54 +0,0 @@ -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) - -### Inversion of Control - -In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we -want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then -we would need to edit every one of our request handlers to call a different constructor of the class. - -The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that -instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done -with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). - -If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is -implemented, it will make sense. - -Change your `Hello` action to the following: - -```php -getAttribute('name', 'Stranger'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: - -```php -$handler = new $className($response); -``` - -Of course we need to also update all the other handlers. - -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/implementation/16-caching/data/pages/09-dependency-injector.md b/implementation/16-caching/data/pages/09-dependency-injector.md deleted file mode 100644 index 7f7c6a2..0000000 --- a/implementation/16-caching/data/pages/09-dependency-injector.md +++ /dev/null @@ -1,213 +0,0 @@ -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) - -### Dependency Injector - -A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when -the class is instantiated. - -Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work -with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look -for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). - -I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) -out of the box. - -After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: - -```php -addDefinitions([ - \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), - \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), -]); - -return $builder->build(); -``` - -In this file we create a containerbuilder, add some definitions to it and return the container. -As the container supports autowiring we only need to define services where we want to use a specific implementation of -an interface. - -In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to -tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second -exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. - -Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), -as we will use that extensively. - -Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally -to create our Handler-Object - -```php -$container = require __DIR__ . '/../config/dependencies.php'; -assert($container instanceof \Psr\Container\ContainerInterface); - -$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); -assert($request instanceof \Psr\Http\Message\ServerRequestInterface); -``` - -The other part that has to be changed is the dispatching of the route. Before you had the following code: - -```php -$className = $routeInfo[1]; -$handler = new $className($response); -assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Change that to the following: - -```php -/** @var RequestHandlerInterface $handler */ -$className = $routeInfo[1]; -$handler = $container->get($className); -assert($handler instanceof RequestHandlerInterface); -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Make sure to use the container fetch the response object in the catch blocks as well: - -```php -} catch (MethodNotAllowed) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(404); - $response->getBody()->write('Not Found'); -} -``` - -Now all your controller constructor dependencies will be automatically resolved with PHP-DI. - -We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons -in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call -`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we -want to work with a different date in a test. - -Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation -that creates us a DateTimeImmutable object with the current date and time. - -src/Service/Time/Now.php: -```php -namespace Lubian\NoFramework\Service\Time; - -interface Now -{ - public function __invoke(): \DateTimeImmutable; -} -``` -src/Service/Time/SystemClockNow.php: -```php -namespace Lubian\NoFramework\Service\Time; - -final class SystemClockNow implements Now -{ - - public function __invoke(): \DateTimeImmutable - { - return new \DateTimeImmutable; - } -} -``` -If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and -update the handle-method to use the new class property: - -```php -getAttribute('name', 'Stranger'); - $nowAsString = ($this->now)()->format('H:i:s'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowAsString); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI -automatically figures out what classes are requested in the constructor and tries to create the objects needed. - -But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred -S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: - -```php - public function __construct( - private ResponseInterface $response, - private Now $now, - ) -``` - -When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation -should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: - -```php -\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), -``` - -we could also use the PHP-DI create method to delegate the object creation to the container implementation: -```php -\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), -``` - -this way the container can try to resolve any dependencies that the class might have internally, but prefer the other -method because we are not depending on this specific dependency injection implementation. - -Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are -requesting the Hello action. - -If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As -we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way -we wont get any warnings for this particular issue, but for any other issues we add to our code. - -Update the phpstan.neon file to include a "baseline" file: - -``` -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` - -if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and -ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just -'baseline' - -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/implementation/16-caching/data/pages/10-invoker.md b/implementation/16-caching/data/pages/10-invoker.md deleted file mode 100644 index 3033fae..0000000 --- a/implementation/16-caching/data/pages/10-invoker.md +++ /dev/null @@ -1,102 +0,0 @@ -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) - -### Invoker - -Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the -one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long -with some services and not the whole Requestobject with all its various properties. - -If we take our Hello action for example we only need a response object, the time service and the 'name' information from -the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named -the class hello and it would be redundant to also call the the method hello. So an updated version of that class could -look like this: - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - string $name = 'Stranger', - ): ResponseInterface - { - $body = $this->response->getBody(); - $nowString = $now->get()->format('H:i:s'); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowString); - return $response - ->withBody($body) - ->withStatus(200); - } -} -``` - -It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short -closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the -rootpath of our application yet. - -```php -$r->addRoute('GET', '/hello[/{name}]', Hello::class); -$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); -$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -``` - -In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the -route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that -class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what -arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router -if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple -callable we would need to manually use reflection as well to resolve all the arguments. - -But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) -which handles all of that for us. - -After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo -switch section in the Bootstrap.php file to use the container->call() method: - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2]; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$response = $container->call($handler, $args); -``` - -Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. - -But by now you should know that I do not like to depend on specific implementations and the call method is not defined in -the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container -or any other implementation. - -Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific -container implementation so we could use it with any container that implements the ContainerInterface. And best of all -the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that -we could implement if we ever want to write our own implementation or we could write an adapter that uses a different -class that solves the same problem. - -But for now we are using the solution provided by PHP-DI. -So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2] ?? []; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$invoker = $container->get(InvokerInterface::class); -assert($invoker instanceof InvokerInterface); -$response = $invoker->call($handler, $args); -assert($response instanceof ResponseInterface); -``` - -Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) -by php, and even some more. - -But let us move on to something more fun and add some templating functionality to our application as we are trying to build -a website in the end. - -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/implementation/16-caching/data/pages/11-templating.md b/implementation/16-caching/data/pages/11-templating.md deleted file mode 100644 index 3759664..0000000 --- a/implementation/16-caching/data/pages/11-templating.md +++ /dev/null @@ -1,240 +0,0 @@ -[<< previous](10-invoker.md) | [next >>](12-configuration.md) - -### Templating - -A template engine is not necessary with PHP because the language itself can take care of that. But it can make things -like escaping values easier. They also make it easier to draw a clear line between your application logic and the -template files which should only put your variables into the HTML code. - -A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please -also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. -Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. - -For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install -that package before you continue (`composer require mustache/mustache`). - -Another well known alternative would be [Twig](http://twig.sensiolabs.org/). - -Now please go and have a look at the source code of the -[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class -does not implement an interface. - -You could just type hint against the concrete class. But the problem with this approach is that you create tight -coupling. - -In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the -implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want -to add functionality to the engine. You can't do that without going back and changing all your code that is tightly -coupled. - -What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need -another implementation, you just implement that interface in your new class and inject the new class instead. - -Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). -This sounds a lot more complicated than it is, so just follow along. - -First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). -This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. -A class can implement multiple interfaces if necessary. - -So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a -new folder in your `src/` folder with the name `Template` where you can put all the template related things. - -In there create a new interface `Renderer.php` that looks like this: - -```php - $data - * @return string - */ - public function render(string $template, array $data = []) : string; -} -``` - -Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file -`MustacheRenderer.php` with the following content: - -```php -engine->render($template, $data); - } -} -``` - -As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple -and only fulfills the interface. - -Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know -which implementation he has to inject when you hint for the interface. Add this line: - -```php -[ - ... - \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) - ->constructor(new Mustache_Engine), -] -``` - -Now update the Hello.php class to require an implementation of our renderer interface -and use that to render a string using mustache syntax. - - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - Renderer $renderer, - string $name = 'Stranger', - ): ResponseInterface { - $body = $response->getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render( - 'Hello {{name}}, the time is {{now}}!', - $data, - ); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} -``` - -Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. -But what we want is template files, so let's go back and change that. - -To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the -`dependencies.php` file and add the following code: - -```php -[ - ... - Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), - Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), -] -``` - -We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. -Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have -to rename all the template files. - -To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the -dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader -in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object -we can then use it in the factory. - -In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: - -``` -

Hello World

-Hello {{ name }} -``` - -Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` - -Navigate to the hello page in your browser to make sure everything works. - -One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies -file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration -values. - -Lets create a 'Settings' class in our './src' Folder: - -```php -addDefinitions([ - Settings::class => fn () => require __DIR__ '/settings.php', - ResponseInterface::class => create(Response::class), - ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), - Renderer::class => fn (ME $me) => new Mustache($me), - MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), -]); - -return $builder->build(); -``` - - - -And as always, don't forget to commit your changes. - - -[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/implementation/16-caching/data/pages/12-configuration.md b/implementation/16-caching/data/pages/12-configuration.md deleted file mode 100644 index a44dfd5..0000000 --- a/implementation/16-caching/data/pages/12-configuration.md +++ /dev/null @@ -1,201 +0,0 @@ -[<< previous](11-templating.md) | [next >>](13-refactoring.md) - -### Configuration - -In the last chapter we added some more definitions to our dependencies.php in that definitions -we needed to pass quite a few configuration settings and filesystem strings to the constructors -of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. - -As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. - -As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. - -Lets create a 'Settings' class in our './src' Folder: - -```php -filePath; - } -} -``` - -If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files -and craft a settings object from them. - -As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another -factory for our Container because everyone should have a Friend :) - -```php -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn () => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} -``` - -For this to work we need to change our dependencies.php file to just return the array of definitions: -And here we can instantly use the Settings object to create our template engine. - -```php - fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), -]; -``` - -Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: -require __DIR__ . '/../vendor/autoload.php'; - -```php -... -error_reporting(E_ALL); - -$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); -$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); -... -``` - -Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. - -[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/implementation/16-caching/data/pages/13-refactoring.md b/implementation/16-caching/data/pages/13-refactoring.md deleted file mode 100644 index 067e168..0000000 --- a/implementation/16-caching/data/pages/13-refactoring.md +++ /dev/null @@ -1,377 +0,0 @@ -[<< previous](12-configuration.md) | [next >>](14-middleware.md) - -### Refactoring - -By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no -reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. -After all the bootstrap file should just set up the classes needed for the handling logic and execute them. - -At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. -As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the -method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non -static and extracted an interface. - -'./src/Http/Emitter.php' -```php -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - echo $response->getBody(); - } -} -``` -After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following -code in the Bootstrap.php to emit the response: - -```php -/** @var Emitter $emitter */ -$emitter = $container->get(Emitter::class); -$emitter->emit($response); -``` - -If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily -write an adapter that implements your emitter interface and wraps that more advanced emitter - -Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and -calling the routerhandler that in the passes the request to a function and gets the response. - -For this to steps to be seperated we are going to create two more classes: -1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object -2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the - requestobject, fetches the correct object from the container and calls it to create a response. - -Lets create the HandlerInterface first: - -```php -getAttribute($this->routeAttributeName, false); - assert($handler !== false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} - -``` - -We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. -The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler -as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in -the next chapter. - -```php -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} -``` - -Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. -```php -[ - '...', - Emitter::class => fn (BasicEmitter $e) => $e, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, -], -``` - -And then we can update our Bootstrap.php to fetch all the services and let them handle the request. - -```php -... -$routeMiddleWare = $container->get(MiddlewareInterface::class); -assert($routeMiddleWare instanceof MiddlewareInterface); -$handler = $container->get(RoutedRequestHandler::class); -assert($handler instanceof RequestHandlerInterface); -$emitter = $container->get(Emitter::class); -assert($emitter instanceof Emitter); - -$request = $container->get(ServerRequestInterface::class); -assert($request instanceof ServerRequestInterface); - -$response = $routeMiddleWare->process($request, $handler); -$emitter->emit($response); -``` -Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of -code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). - -So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. - -I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class -should require to function properly. - -* A RequestFactory - We want our Kernel to be able to build the request itself -* An Emitter - Without an Emitter we will not be able to send the response to the client -* RouteMiddleware - To decore the request with the correct handler for the requested route -* RequestHandler - To delegate the request to the correct funtion that creates the response - -As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface -to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our -interface: - -```php -factory::fromGlobals(); - } - - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} -``` - -For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: - -```php -routeMiddleware->process($request, $this->handler); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} - -``` - -We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines - -```php -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); -``` - -You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking -at the Whoops output and adding the needed definitions to the dependencies.php file. - -And as always, don't forget to commit your changes. - -[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/implementation/16-caching/data/pages/14-middleware.md b/implementation/16-caching/data/pages/14-middleware.md deleted file mode 100644 index e698327..0000000 --- a/implementation/16-caching/data/pages/14-middleware.md +++ /dev/null @@ -1,298 +0,0 @@ -[<< previous](12-refactoring.md) | [next >>](14-invoker.md) - -### Middleware - -In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain -a bit more about what this interface does and why it is used in many applications. - -The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets -passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all -the middlewars to the client/emitter. - -So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the -response after it gets created by our handlers. - -So lets take a look at the middleware and the requesthandler interfaces - -```php -interface MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; -} - -interface RequestHandlerInterface -{ - public function handle(ServerRequestInterface $request): ResponseInterface; -} -``` - -The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a -requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the -response. - -But the middleware could just ignore the handler and produce a response on its own as the interface just requires us -to produce a response. - -A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users -that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the -database. - -In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates -the request with an 'isAuthenticated' attribute. - -If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that -response is not already cached, than we let the handler create the response and store that in the cache for a few -seconds - -```php -interface CacheInterface -{ - public function get(string $key, callable $resolver, int $ttl): mixed; -} -``` - -The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one -defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses -the callable to produce the value and stores it for the time specified in ttl. - -so lets write our caching middleware: - -```php -final class CachingMiddleware implements MiddlewareInterface -{ - public function __construct(private CacheInterface $cache){} - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { - $key = $request->getUri()->getPath(); - return $this->cache->get($key, fn() => $handler->handle($request), 10); - } - return $handler->handle($request); - } -} -``` - -we can also modify the response after it has been created by our application, for example we could implement a gzip -middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser -know that our application is used to serve dank memes: - -```php -final class DankMemeMiddleware implements MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - $response = $handler->handle($request); - return $response->withAddedHeader('Meme', 'Dank'); - } -} -``` - -but for our application we are going to just add two external middlewares: - -* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. -* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware - -```bash -composer require middlewares/trailing-slash -composer require middlewares/whoops -``` - -The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the -application as well as the middleware stack. - -Our desired request -> response flow looks something like this: - - Client - | ^ - v | - Kernel - | ^ - v | - Whoops Middleware - | ^ - v | - TrailingSlash - | ^ - v | - Routing - | ^ - v | - ContainerResolver - | ^ - v | - Controller/Action - -As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every -middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. - -```php -interface Pipeline -{ - public function dispatch(ServerRequestInterface $request): ResponseInterface; -} -``` - -And our implementation looks something like this: - -```php - $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} -``` - -Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code -that should produce our response. - -In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument -and store that itself as the current tip. - -There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) - -Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline -Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline - -```php -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} -``` - -And configure the container to use the Factory to create the Pipeline: - -```php - ..., - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - ... -``` -And of course a new file called middlewares.php in our config folder: -```php -pipeline->dispatch($request); -} -``` - -Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our -pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a -dependency container, if you want you can do that as well. - -[<< previous](12-refactoring.md) | [next >>](14-invoker.md) diff --git a/implementation/16-caching/phpstan-baseline.neon b/implementation/16-caching/phpstan-baseline.neon deleted file mode 100644 index 61697a1..0000000 --- a/implementation/16-caching/phpstan-baseline.neon +++ /dev/null @@ -1,7 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" - count: 1 - path: src/Http/InvokerRoutedHandler.php - diff --git a/implementation/16-caching/phpstan.neon b/implementation/16-caching/phpstan.neon deleted file mode 100644 index 2eac45a..0000000 --- a/implementation/16-caching/phpstan.neon +++ /dev/null @@ -1,8 +0,0 @@ -includes: - - phpstan-baseline.neon - -parameters: - level: max - paths: - - src - - config \ No newline at end of file diff --git a/implementation/16-caching/public/css/spectre-exp.min.css b/implementation/16-caching/public/css/spectre-exp.min.css deleted file mode 100644 index d313774..0000000 --- a/implementation/16-caching/public/css/spectre-exp.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/implementation/16-caching/public/css/spectre-icons.min.css b/implementation/16-caching/public/css/spectre-icons.min.css deleted file mode 100644 index 0276f7b..0000000 --- a/implementation/16-caching/public/css/spectre-icons.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/implementation/16-caching/public/css/spectre.min.css b/implementation/16-caching/public/css/spectre.min.css deleted file mode 100644 index 0fe23d9..0000000 --- a/implementation/16-caching/public/css/spectre.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/implementation/16-caching/public/favicon.ico b/implementation/16-caching/public/favicon.ico deleted file mode 100644 index 09499b8b3b3201e0f50088e3ac42e167778d1153..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/16-caching/public/index.php b/implementation/16-caching/public/index.php deleted file mode 100644 index d93da3a..0000000 --- a/implementation/16-caching/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render('hello', $data); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/16-caching/src/Action/Other.php b/implementation/16-caching/src/Action/Other.php deleted file mode 100644 index 895796e..0000000 --- a/implementation/16-caching/src/Action/Other.php +++ /dev/null @@ -1,19 +0,0 @@ -getBody(); - - $body->write('This works too!'); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/16-caching/src/Action/Page.php b/implementation/16-caching/src/Action/Page.php deleted file mode 100644 index e3461ad..0000000 --- a/implementation/16-caching/src/Action/Page.php +++ /dev/null @@ -1,35 +0,0 @@ -byTitle($page); - $content = $this->linkFilter($page->content); - $content = $parsedown->parse($content); - $html = $renderer->render('page', ['content' => $content, 'title' => $page->title]); - $response->getBody()->write($html); - return $response; - } - - private function linkFilter(string $content): string - { - $content = preg_replace('/\(\d\d-/m', '(', $content); - return str_replace('.md)', ')', $content); - } -} diff --git a/implementation/16-caching/src/Bootstrap.php b/implementation/16-caching/src/Bootstrap.php deleted file mode 100644 index 3abc2e5..0000000 --- a/implementation/16-caching/src/Bootstrap.php +++ /dev/null @@ -1,40 +0,0 @@ -getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); diff --git a/implementation/16-caching/src/Exception/InternalServerError.php b/implementation/16-caching/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/implementation/16-caching/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ -factory::fromGlobals(); - } - - /** - * @param UriInterface|string $uri - * @param array $serverParams - */ - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} diff --git a/implementation/16-caching/src/Factory/FileSystemSettingsProvider.php b/implementation/16-caching/src/Factory/FileSystemSettingsProvider.php deleted file mode 100644 index f071078..0000000 --- a/implementation/16-caching/src/Factory/FileSystemSettingsProvider.php +++ /dev/null @@ -1,22 +0,0 @@ -filePath; - assert($settings instanceof Settings); - return $settings; - } -} diff --git a/implementation/16-caching/src/Factory/PipelineProvider.php b/implementation/16-caching/src/Factory/PipelineProvider.php deleted file mode 100644 index 77738f8..0000000 --- a/implementation/16-caching/src/Factory/PipelineProvider.php +++ /dev/null @@ -1,25 +0,0 @@ -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} diff --git a/implementation/16-caching/src/Factory/RequestFactory.php b/implementation/16-caching/src/Factory/RequestFactory.php deleted file mode 100644 index 2b17abc..0000000 --- a/implementation/16-caching/src/Factory/RequestFactory.php +++ /dev/null @@ -1,11 +0,0 @@ -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn (): Settings => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} diff --git a/implementation/16-caching/src/Factory/SettingsProvider.php b/implementation/16-caching/src/Factory/SettingsProvider.php deleted file mode 100644 index ce1c5f0..0000000 --- a/implementation/16-caching/src/Factory/SettingsProvider.php +++ /dev/null @@ -1,10 +0,0 @@ -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - - echo $response->getBody(); - } -} diff --git a/implementation/16-caching/src/Http/ContainerPipeline.php b/implementation/16-caching/src/Http/ContainerPipeline.php deleted file mode 100644 index 816cedd..0000000 --- a/implementation/16-caching/src/Http/ContainerPipeline.php +++ /dev/null @@ -1,82 +0,0 @@ - $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} diff --git a/implementation/16-caching/src/Http/Emitter.php b/implementation/16-caching/src/Http/Emitter.php deleted file mode 100644 index ce4c035..0000000 --- a/implementation/16-caching/src/Http/Emitter.php +++ /dev/null @@ -1,10 +0,0 @@ -getAttribute($this->routeAttributeName, false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} diff --git a/implementation/16-caching/src/Http/Pipeline.php b/implementation/16-caching/src/Http/Pipeline.php deleted file mode 100644 index 1a9dcda..0000000 --- a/implementation/16-caching/src/Http/Pipeline.php +++ /dev/null @@ -1,11 +0,0 @@ -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} diff --git a/implementation/16-caching/src/Http/RoutedRequestHandler.php b/implementation/16-caching/src/Http/RoutedRequestHandler.php deleted file mode 100644 index a7407c9..0000000 --- a/implementation/16-caching/src/Http/RoutedRequestHandler.php +++ /dev/null @@ -1,10 +0,0 @@ -pipeline->dispatch($request); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} diff --git a/implementation/16-caching/src/Middleware/CacheMiddleware.php b/implementation/16-caching/src/Middleware/CacheMiddleware.php deleted file mode 100644 index edf67d7..0000000 --- a/implementation/16-caching/src/Middleware/CacheMiddleware.php +++ /dev/null @@ -1,36 +0,0 @@ -getMethod() === 'GET') { - $key = (string) $request->getUri(); - $key = base64_encode($key); - $callback = fn () => $handler->handle($request); - $cached = $this->cache->get($key, function (ItemInterface $item) use ($callback) { - $item->expiresAfter(120); - $response = $callback(); - return $this->serializer::toString($response); - }); - return $this->serializer::fromString($cached); - } - return $handler->handle($request); - } -} diff --git a/implementation/16-caching/src/Model/MarkdownPage.php b/implementation/16-caching/src/Model/MarkdownPage.php deleted file mode 100644 index 503774f..0000000 --- a/implementation/16-caching/src/Model/MarkdownPage.php +++ /dev/null @@ -1,13 +0,0 @@ - $this->repo->all(); - return $this->cache->get('ALLPAGES', function (ItemInterface $item) use ($callback) { - $item->expiresAfter(30); - return $callback(); - }); - } - - public function byId(int $id): MarkdownPage - { - $callback = fn () => $this->repo->byId($id); - return $this->cache->get('PAGE' . $id, function (ItemInterface $item) use ($callback) { - $item->expiresAfter(30); - return $callback(); - }); - } - - public function byTitle(string $title): MarkdownPage - { - $callback = fn () => $this->repo->byTitle($title); - return $this->cache->get('PAGE' . $title, function (ItemInterface $item) use ($callback) { - $item->expiresAfter(30); - return $callback(); - }); - } -} diff --git a/implementation/16-caching/src/Repository/MarkdownPageFilesystem.php b/implementation/16-caching/src/Repository/MarkdownPageFilesystem.php deleted file mode 100644 index abd4107..0000000 --- a/implementation/16-caching/src/Repository/MarkdownPageFilesystem.php +++ /dev/null @@ -1,63 +0,0 @@ -dataPath . '*.md'); - assert(is_array($fileNames)); - return array_map(function (string $name): MarkdownPage { - usleep(100000); - $content = file_get_contents($name); - $name = str_replace($this->dataPath, '', $name); - $name = str_replace('.md', '', $name); - $id = (int) substr($name, 0, 2); - $title = substr($name, 3); - return new MarkdownPage($id, $title, $content); - }, $fileNames); - } - - public function byId(int $id): MarkdownPage - { - $callback = fn (MarkdownPage $p): bool => $p->id === $id; - $filtered = array_values(array_filter($this->all(), $callback)); - if (count($filtered) === 0) { - throw new NotFound; - } - return $filtered[0]; - } - - public function byTitle(string $title): MarkdownPage - { - $callback = fn (MarkdownPage $p): bool => $p->title === $title; - $filtered = array_values(array_filter($this->all(), $callback)); - if (count($filtered) === 0) { - throw new NotFound; - } - return $filtered[0]; - } -} diff --git a/implementation/16-caching/src/Repository/MarkdownPageRepo.php b/implementation/16-caching/src/Repository/MarkdownPageRepo.php deleted file mode 100644 index b823af0..0000000 --- a/implementation/16-caching/src/Repository/MarkdownPageRepo.php +++ /dev/null @@ -1,17 +0,0 @@ -engine->render($template, $data); - } -} diff --git a/implementation/16-caching/src/Template/Renderer.php b/implementation/16-caching/src/Template/Renderer.php deleted file mode 100644 index ff916ed..0000000 --- a/implementation/16-caching/src/Template/Renderer.php +++ /dev/null @@ -1,11 +0,0 @@ - $data - */ - public function render(string $template, array $data = []): string; -} diff --git a/implementation/16-caching/templates/hello.html b/implementation/16-caching/templates/hello.html deleted file mode 100644 index 15a4cd2..0000000 --- a/implementation/16-caching/templates/hello.html +++ /dev/null @@ -1,6 +0,0 @@ -{{> partials/head }} -
-

Hello {{name}}

-

The time is {{now}}

-
-{{> partials/foot }} diff --git a/implementation/16-caching/templates/page.html b/implementation/16-caching/templates/page.html deleted file mode 100644 index c3c5284..0000000 --- a/implementation/16-caching/templates/page.html +++ /dev/null @@ -1,5 +0,0 @@ -{{> partials/head }} -
- {{{content}}} -
-{{> partials/foot }} diff --git a/implementation/16-caching/templates/partials/foot.html b/implementation/16-caching/templates/partials/foot.html deleted file mode 100644 index 17c7245..0000000 --- a/implementation/16-caching/templates/partials/foot.html +++ /dev/null @@ -1,3 +0,0 @@ -
- - \ No newline at end of file diff --git a/implementation/16-caching/templates/partials/head.html b/implementation/16-caching/templates/partials/head.html deleted file mode 100644 index 421d387..0000000 --- a/implementation/16-caching/templates/partials/head.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - No Framework: {{title}} - - - - - -
diff --git a/implementation/16-data-repository/.php-cs-fixer.php b/implementation/16-data-repository/.php-cs-fixer.php deleted file mode 100644 index 705a7d7..0000000 --- a/implementation/16-data-repository/.php-cs-fixer.php +++ /dev/null @@ -1,38 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config' - ]) - ); \ No newline at end of file diff --git a/implementation/16-data-repository/.phpcs.xml.dist b/implementation/16-data-repository/.phpcs.xml.dist deleted file mode 100644 index 3b433f6..0000000 --- a/implementation/16-data-repository/.phpcs.xml.dist +++ /dev/null @@ -1,9 +0,0 @@ - - - - - src - config - - - \ No newline at end of file diff --git a/implementation/16-data-repository/cli-config.php b/implementation/16-data-repository/cli-config.php deleted file mode 100644 index fbc6598..0000000 --- a/implementation/16-data-repository/cli-config.php +++ /dev/null @@ -1,13 +0,0 @@ -getContainer(); - -return ConsoleRunner::createHelperSet($container->get(EntityManagerInterface::class)); diff --git a/implementation/16-data-repository/composer.json b/implementation/16-data-repository/composer.json deleted file mode 100644 index b5c7f1a..0000000 --- a/implementation/16-data-repository/composer.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3", - "mustache/mustache": "^2.14", - "psr/http-server-middleware": "^1.0", - "middlewares/trailing-slash": "^2.0", - "middlewares/whoops": "^2.0", - "erusev/parsedown": "^1.7", - "league/commonmark": "^2.2" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require-dev": { - "phpstan/phpstan": "^1.5", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0", - "squizlabs/php_codesniffer": "^3.6", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1", - "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0" - }, - "config": { - "allow-plugins": { - "phpstan/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "serve": [ - "Composer\\Config::disableProcessTimeout", - "php -S 0.0.0.0:1234 -t public" - ], - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" - } -} diff --git a/implementation/16-data-repository/composer.lock b/implementation/16-data-repository/composer.lock deleted file mode 100644 index a62d9c7..0000000 --- a/implementation/16-data-repository/composer.lock +++ /dev/null @@ -1,2438 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "00acf07ae222f9117a84bce157b99837", - "packages": [ - { - "name": "dflydev/dot-access-data", - "version": "v3.0.1", - "source": { - "type": "git", - "url": "https://github.com/dflydev/dflydev-dot-access-data.git", - "reference": "0992cc19268b259a39e86f296da5f0677841f42c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", - "reference": "0992cc19268b259a39e86f296da5f0677841f42c", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.42", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", - "scrutinizer/ocular": "1.6.0", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^3.14" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Dflydev\\DotAccessData\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dragonfly Development Inc.", - "email": "info@dflydev.com", - "homepage": "http://dflydev.com" - }, - { - "name": "Beau Simensen", - "email": "beau@dflydev.com", - "homepage": "http://beausimensen.com" - }, - { - "name": "Carlos Frutos", - "email": "carlos@kiwing.it", - "homepage": "https://github.com/cfrutos" - }, - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com" - } - ], - "description": "Given a deep data structure, access data by dot notation.", - "homepage": "https://github.com/dflydev/dflydev-dot-access-data", - "keywords": [ - "access", - "data", - "dot", - "notation" - ], - "support": { - "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", - "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" - }, - "time": "2021-08-13T13:06:58+00:00" - }, - { - "name": "erusev/parsedown", - "version": "1.7.4", - "source": { - "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35" - }, - "type": "library", - "autoload": { - "psr-0": { - "Parsedown": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com" - } - ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org", - "keywords": [ - "markdown", - "parser" - ], - "support": { - "issues": "https://github.com/erusev/parsedown/issues", - "source": "https://github.com/erusev/parsedown/tree/1.7.x" - }, - "time": "2019-12-30T22:54:17+00:00" - }, - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.9.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2022-03-29T20:12:16+00:00" - }, - { - "name": "league/commonmark", - "version": "2.2.3", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/commonmark.git", - "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", - "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "league/config": "^1.1.1", - "php": "^7.4 || ^8.0", - "psr/event-dispatcher": "^1.0", - "symfony/deprecation-contracts": "^2.1 || ^3.0", - "symfony/polyfill-php80": "^1.15" - }, - "require-dev": { - "cebe/markdown": "^1.0", - "commonmark/cmark": "0.30.0", - "commonmark/commonmark.js": "0.30.0", - "composer/package-versions-deprecated": "^1.8", - "erusev/parsedown": "^1.0", - "ext-json": "*", - "github/gfm": "0.29.0", - "michelf/php-markdown": "^1.4", - "phpstan/phpstan": "^0.12.88 || ^1.0.0", - "phpunit/phpunit": "^9.5.5", - "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", - "unleashedtech/php-coding-standard": "^3.1", - "vimeo/psalm": "^4.7.3" - }, - "suggest": { - "symfony/yaml": "v2.3+ required if using the Front Matter extension" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.3-dev" - } - }, - "autoload": { - "psr-4": { - "League\\CommonMark\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com", - "role": "Lead Developer" - } - ], - "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", - "homepage": "https://commonmark.thephpleague.com", - "keywords": [ - "commonmark", - "flavored", - "gfm", - "github", - "github-flavored", - "markdown", - "md", - "parser" - ], - "support": { - "docs": "https://commonmark.thephpleague.com/", - "forum": "https://github.com/thephpleague/commonmark/discussions", - "issues": "https://github.com/thephpleague/commonmark/issues", - "rss": "https://github.com/thephpleague/commonmark/releases.atom", - "source": "https://github.com/thephpleague/commonmark" - }, - "funding": [ - { - "url": "https://www.colinodell.com/sponsor", - "type": "custom" - }, - { - "url": "https://www.paypal.me/colinpodell/10.00", - "type": "custom" - }, - { - "url": "https://github.com/colinodell", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/commonmark", - "type": "tidelift" - } - ], - "time": "2022-02-26T21:24:45+00:00" - }, - { - "name": "league/config", - "version": "v1.1.1", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/config.git", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "shasum": "" - }, - "require": { - "dflydev/dot-access-data": "^3.0.1", - "nette/schema": "^1.2", - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.90", - "phpunit/phpunit": "^9.5.5", - "scrutinizer/ocular": "^1.8.1", - "unleashedtech/php-coding-standard": "^3.1", - "vimeo/psalm": "^4.7.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.2-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Config\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com", - "role": "Lead Developer" - } - ], - "description": "Define configuration arrays with strict schemas and access values with dot notation", - "homepage": "https://config.thephpleague.com", - "keywords": [ - "array", - "config", - "configuration", - "dot", - "dot-access", - "nested", - "schema" - ], - "support": { - "docs": "https://config.thephpleague.com/", - "issues": "https://github.com/thephpleague/config/issues", - "rss": "https://github.com/thephpleague/config/releases.atom", - "source": "https://github.com/thephpleague/config" - }, - "funding": [ - { - "url": "https://www.colinodell.com/sponsor", - "type": "custom" - }, - { - "url": "https://www.paypal.me/colinpodell/10.00", - "type": "custom" - }, - { - "url": "https://github.com/colinodell", - "type": "github" - } - ], - "time": "2021-08-14T12:15:32+00:00" - }, - { - "name": "middlewares/trailing-slash", - "version": "v2.0.1", - "source": { - "type": "git", - "url": "https://github.com/middlewares/trailing-slash.git", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", - "shasum": "" - }, - "require": { - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to normalize the trailing slash of the uri path", - "homepage": "https://github.com/middlewares/trailing-slash", - "keywords": [ - "http", - "middleware", - "normalize", - "path", - "psr-15", - "psr-7", - "slash" - ], - "support": { - "issues": "https://github.com/middlewares/trailing-slash/issues", - "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" - }, - "time": "2020-12-02T00:06:55+00:00" - }, - { - "name": "middlewares/utils", - "version": "v3.3.0", - "source": { - "type": "git", - "url": "https://github.com/middlewares/utils.git", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^v2.16", - "guzzlehttp/psr7": "^2.0", - "laminas/laminas-diactoros": "^2.4", - "nyholm/psr7": "^1.0", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "slim/psr7": "^1.4", - "squizlabs/php_codesniffer": "^3.5", - "sunrise/http-message": "^1.0", - "sunrise/http-server-request": "^1.0", - "sunrise/stream": "^1.0.15", - "sunrise/uri": "^1.0.15" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\Utils\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Common utils for PSR-15 middleware packages", - "homepage": "https://github.com/middlewares/utils", - "keywords": [ - "PSR-11", - "http", - "middleware", - "psr-15", - "psr-17", - "psr-7" - ], - "support": { - "issues": "https://github.com/middlewares/utils/issues", - "source": "https://github.com/middlewares/utils/tree/v3.3.0" - }, - "time": "2021-07-04T17:56:23+00:00" - }, - { - "name": "middlewares/whoops", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/middlewares/whoops.git", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", - "shasum": "" - }, - "require": { - "filp/whoops": "^2.5", - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "eloquent/phony-phpunit": "^5.0 || ^7.0", - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to use Whoops as error handler", - "homepage": "https://github.com/middlewares/whoops", - "keywords": [ - "error", - "http", - "middleware", - "psr-15", - "psr-7", - "server", - "whoops" - ], - "support": { - "issues": "https://github.com/middlewares/whoops/issues", - "source": "https://github.com/middlewares/whoops/tree/v2.0.2" - }, - "time": "2022-01-27T20:31:30+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nette/schema", - "version": "v1.2.2", - "source": { - "type": "git", - "url": "https://github.com/nette/schema.git", - "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", - "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", - "shasum": "" - }, - "require": { - "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": ">=7.1 <8.2" - }, - "require-dev": { - "nette/tester": "^2.3 || ^2.4", - "phpstan/phpstan-nette": "^0.12", - "tracy/tracy": "^2.7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "📐 Nette Schema: validating data structures against a given Schema.", - "homepage": "https://nette.org", - "keywords": [ - "config", - "nette" - ], - "support": { - "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.2" - }, - "time": "2021-10-15T11:40:02+00:00" - }, - { - "name": "nette/utils", - "version": "v3.2.7", - "source": { - "type": "git", - "url": "https://github.com/nette/utils.git", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", - "shasum": "" - }, - "require": { - "php": ">=7.2 <8.2" - }, - "conflict": { - "nette/di": "<3.0.6" - }, - "require-dev": { - "nette/tester": "~2.0", - "phpstan/phpstan": "^1.0", - "tracy/tracy": "^2.3" - }, - "suggest": { - "ext-gd": "to use Image", - "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", - "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", - "ext-json": "to use Nette\\Utils\\Json", - "ext-mbstring": "to use Strings::lower() etc...", - "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", - "ext-xml": "to use Strings::length() etc. when mbstring is not available" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", - "homepage": "https://nette.org", - "keywords": [ - "array", - "core", - "datetime", - "images", - "json", - "nette", - "paginator", - "password", - "slugify", - "string", - "unicode", - "utf-8", - "utility", - "validation" - ], - "support": { - "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.2.7" - }, - "time": "2022-01-24T11:29:14+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/http-server-middleware", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-middleware.git", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0", - "psr/http-server-handler": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side middleware", - "keywords": [ - "http", - "http-interop", - "middleware", - "psr", - "psr-15", - "psr-7", - "request", - "response" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-middleware/issues", - "source": "https://github.com/php-fig/http-server-middleware/tree/master" - }, - "time": "2018-10-30T17:12:04+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.0.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "shasum": "" - }, - "require": { - "php": ">=8.0.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:55:41+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-04T08:16:47+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "doctrine/coding-standard", - "version": "8.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/coding-standard.git", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "slevomat/coding-standard": "^6.4.1", - "squizlabs/php_codesniffer": "^3.5.8" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Steve Müller", - "email": "st.mueller@dzh-online.de" - } - ], - "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", - "keywords": [ - "checks", - "code", - "coding", - "cs", - "doctrine", - "rules", - "sniffer", - "sniffs", - "standard", - "style" - ], - "support": { - "issues": "https://github.com/doctrine/coding-standard/issues", - "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" - }, - "time": "2021-04-03T10:54:55+00:00" - }, - { - "name": "mnapoli/hard-mode", - "version": "0.3.0", - "source": { - "type": "git", - "url": "https://github.com/mnapoli/hard-mode.git", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", - "shasum": "" - }, - "require": { - "doctrine/coding-standard": "^8.0" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Hard mode for PHP", - "support": { - "issues": "https://github.com/mnapoli/hard-mode/issues", - "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" - }, - "time": "2020-10-12T07:54:37+00:00" - }, - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.9", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.26", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/master" - }, - "time": "2020-08-03T20:32:43+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.5.4", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", - "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.4" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-04-03T12:39:00+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^1.2.0" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" - }, - "time": "2021-11-18T09:30:29+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "6.4.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpstan/phpstan": "0.12.48", - "phpstan/phpstan-deprecation-rules": "0.12.5", - "phpstan/phpstan-phpunit": "0.12.16", - "phpstan/phpstan-strict-rules": "0.12.5", - "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2020-10-05T12:39:37+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.3.0" -} diff --git a/implementation/16-data-repository/config/dependencies.php b/implementation/16-data-repository/config/dependencies.php deleted file mode 100644 index 0040933..0000000 --- a/implementation/16-data-repository/config/dependencies.php +++ /dev/null @@ -1,55 +0,0 @@ - fn (SystemClockNow $n) => $n, - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, - Emitter::class => fn (BasicEmitter $e) => $e, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, - CacheInterface::class => fn (FilesystemAdapter $a) => $a, - MarkdownParser::class => fn (ParsedownParser $p) => $p, - MarkdownPageRepo::class => fn (FileSystemMarkdownPageRepo $r) => $r, - - // Factories - ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), - Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), -]; diff --git a/implementation/16-data-repository/config/middlewares.php b/implementation/16-data-repository/config/middlewares.php deleted file mode 100644 index 71dd461..0000000 --- a/implementation/16-data-repository/config/middlewares.php +++ /dev/null @@ -1,11 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/page', [Page::class, 'list']); - $r->addRoute('GET', '/page/{page}', [Page::class, 'show']); - $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); - $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -}; diff --git a/implementation/16-data-repository/config/settings.php b/implementation/16-data-repository/config/settings.php deleted file mode 100644 index c654565..0000000 --- a/implementation/16-data-repository/config/settings.php +++ /dev/null @@ -1,12 +0,0 @@ ->](02-composer.md) - -### Front Controller - -A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. - -To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. - -A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. - -The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. - -But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. - -So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. - -Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: - -```php ->](02-composer.md) diff --git a/implementation/16-data-repository/data/pages/02-composer.md b/implementation/16-data-repository/data/pages/02-composer.md deleted file mode 100644 index a25a4a8..0000000 --- a/implementation/16-data-repository/data/pages/02-composer.md +++ /dev/null @@ -1,75 +0,0 @@ -[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) - -### Composer - -[Composer](https://getcomposer.org/) is a dependency manager for PHP. - -Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do -something. With Composer, you can install third-party libraries for your application. - -If you don't have Composer installed already, head over to the website and install it. You can find Composer packages -for your project on [Packagist](https://packagist.org/). - -Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will -be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. - -Add the following content to the file: - -```json -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubiana", - "email": "lubiana@hannover.ccc.de" - } - ] -} -``` - -In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use -whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. -Just replace it with your namespace in your own code. - -I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. - -As the Bootstrap.php file is placed in that directory we should -add the namespace to the File as well. Here is my current Bootstrap.php -as a reference: - -```php ->](03-error-handler.md) diff --git a/implementation/16-data-repository/data/pages/03-error-handler.md b/implementation/16-data-repository/data/pages/03-error-handler.md deleted file mode 100644 index 60465d0..0000000 --- a/implementation/16-data-repository/data/pages/03-error-handler.md +++ /dev/null @@ -1,79 +0,0 @@ -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) - -### Error Handler - -An error handler allows you to customize what happens if your code results in an error. - -A nice error page with a lot of information for debugging goes a long way during development. So the first package -for your application will take care of that. - -I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. -If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, -you have total control over your project. - -An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) - -To install a new package, open up your `composer.json` and add the package to the require part. It should now look -like this: - -```php -"require": { - "php": ">=8.1.0", - "filp/whoops": "^2.14" -}, -``` - -Now run `composer update` in your console and it will be installed. - -Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, -i that case composer automatically installs the package and updates your composer.json-file. - -But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, -ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you -only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. - -**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message -can help someone to gain access to your system. Always show a user friendly error page instead and send an email to -yourself, write to a log or something similar. So only you can see the errors in the production environment. - -For development that does not make sense though -- you want a nice error page. The solution is to have an environment -switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in -case no environment has been set. - -Then after the error handler registration, throw an `Exception` to test if everything is working correctly. -Your `Bootstrap.php` should now look similar to this: - -```php -pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (\Throwable $e) { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -throw new \Exception("Ooooopsie"); - -``` - -You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until -you get it working. Now would also be a good time for another commit. - - -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/implementation/16-data-repository/data/pages/04-development-helpers.md b/implementation/16-data-repository/data/pages/04-development-helpers.md deleted file mode 100644 index 9505284..0000000 --- a/implementation/16-data-repository/data/pages/04-development-helpers.md +++ /dev/null @@ -1,260 +0,0 @@ -[<< previous](03-error-handler.md) | [next >>](05-http.md) - -### Development Helpers - -I have added some more helpers to my composer.json that help me with development. As these are scripts and programms -used only for development they should not be used in a production environment. Composer has a specific sections in its -file called "dev-dependencies", everything that is required in this section does not get installen in production. - -Let's install our dev-helpers and i will explain them one by one: -`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` - -#### Static Code Analysis with phpstan - -Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or -create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements -that are not (or not always) available, if-statements that always are true and a lot of other stuff. - -A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. - -```php -/** - * @param \DateTime $date - * @return void - */ -function printDate($date) { - $date->format('Y-m-d H:i:s'); -} - -printDate('now'); -``` -if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` - -It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has -no use, and secondly, that we are calling the function with a string instead of a datetime object. - -```shell -1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - ------ --------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ --------------------------------------------------------------------------------------------- -30 Call to method DateTime::format() on a separate line has no effect. -33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. - ------ --------------------------------------------------------------------------------------------- -``` - -The second error is something that "declare strict-types" already catches for us, but the first error is something that -we usually would not discover easily without speccially looking for this errortype. - -We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and -path everytime we want to check our code for errors: - -```yaml -parameters: - level: max - paths: - - src -``` -now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project - -With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us -some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. - -```shell -composer require --dev phpstan/extension-installer -composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules -``` - -During the first install you need to allow the extension installer to actually install the extension. The second command -installs some more strict rulesets and activates them in phpstan. - -If we now rerun phpstan it already tells us about some errors we have made: - -``` - ------ ----------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ ----------------------------------------------------------------------------------------------- -10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider - using long ternary. -25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: - http://bit.ly/subtypeexception -26 Unreachable statement - code above always terminates. - ------ ----------------------------------------------------------------------------------------------- -``` - -The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove -that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific -problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working -on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages -everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we -are adding to our code. - -In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the -phpstan.neon file and run phpstan with the -'--generate-baseline' option: - -```yaml -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` -```shell -[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline -Note: Using configuration file /home/vagrant/app/phpstan.neon. - 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - - - [OK] Baseline generated with 1 error. - - -``` - -you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) - -#### PHP-CS-Fixer - -Another great tool is the php-cs-fixer, which just applies a specific style to your code. - -when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current -directory. - -You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) - -personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup -a configuration file that i use in all my projects .php-cs-fixer.php: - -```php -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - ]) - ); -``` - -#### PHP Codesniffer - -The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra -rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. - -it provides the `phpcs` command to check for violations and the `phpcbf` command to actually fix most of the violations. - -Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have -guessed we are adding some extra rules. - -Lets install the ruleset with composer -```shell -composer require --dev mnapoli/hard-mode -``` - -and add a configuration file to actually use it '.phpcs.xml.dist' -```xml - - - - - src - - - -``` - -running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. - -``` -[vagrant@archlinux app]$ ./vendor/bin/phpcs - -FILE: src/Bootstrap.php ----------------------------------------------------------------------------------------------------- -FOUND 4 ERRORS AFFECTING 4 LINES ----------------------------------------------------------------------------------------------------- - 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. - 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead ----------------------------------------------------------------------------------------------------- -PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY ----------------------------------------------------------------------------------------------------- - -Time: 639ms; Memory: 10MB -``` - -You can then use `./vendor/bin/phpcbf` to try to fix them. - - -#### Symfony Var-Dumper - -another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small -functions. - -dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects -or arrays. - -dd() on the other hand is a function that dumps its parameters and then exits the php-script. - -you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. - -#### Composer scripts - -now we have a few commands that are available on the command line. i personally do not like to type complex commands -with lots of parameters by hand all the time, so i added a few lines to my composer.json: - -```json -"scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" -}, -``` - -that way i can just type "composer" followed by the command name in the root of my project. if i want to start the -php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the -time. - -You could also configure PhpStorm to automatically run these commands in the background and highlight the violations -directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my -flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. - -My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before -commiting and pushing the code. - -[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/implementation/16-data-repository/data/pages/05-http.md b/implementation/16-data-repository/data/pages/05-http.md deleted file mode 100644 index 6166214..0000000 --- a/implementation/16-data-repository/data/pages/05-http.md +++ /dev/null @@ -1,124 +0,0 @@ -[<< previous](04-development-helpers.md) | [next >>](06-router.md) - -### HTTP - -PHP already has a few things built in to make working with HTTP easier. For example there are the -[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. - -These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, -if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, -then you will want a class with a nice object-oriented interface that you can use in your application instead. - -Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The -standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php -projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. - -As this is a widely adopted standard there are already several implementations available for us to use. I will choose -the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. - -Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a -[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. - -Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use -that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding -the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). - - -to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit -enter - -Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): - -```php -$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); -$response = new \Laminas\Diactoros\Response; -$response->getBody()->write('Hello World! '); -$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); -``` - -This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. - -In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the -write()-Method on that object. - - -To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: - -```php -echo $response->getBody(); -``` - -This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only -stores data. - -You can play around with the other methods of the Request object and take a look at its content with the dd() function. - -```php -dd($response) -``` - -Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot -be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which -creates a copy of the request object with the changed property and returns that clone: - -```php -$response = $response->withStatus(200); -$response = $response->withAddedHeader('Content-type', 'application/json'); -``` - -If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been -defined this way. - -But if you have been keeping attention you might argue that the following line should not work if the request object is -immutable. - -```php -$response->getBody()->write('Hello World!'); -``` - -The response-body implements a stream interface which is immutable for some reasons that are described in the -[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be -aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in -the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. -I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content -to a response object. - -```php -$body = $response->getBody(); -$body->write('Hello World!'); -$response = $response->withBody($body); -``` - -Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our -output-logic a little bit more. Replace the line that echos the response-body with the following: - -```php -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); -``` - -This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a -webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. - -Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. - -Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter - - -[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/implementation/16-data-repository/data/pages/06-router.md b/implementation/16-data-repository/data/pages/06-router.md deleted file mode 100644 index 6c39ae5..0000000 --- a/implementation/16-data-repository/data/pages/06-router.md +++ /dev/null @@ -1,101 +0,0 @@ -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) - -### Router - -A router dispatches to different handlers depending on rules that you have set up. - -With your current setup it does not matter what URL is used to access the application, it will always result in the same -response. So let's fix that now. - -I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own -favorite package. - -Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) - -By now you know how to install Composer packages, so I will leave that to you. - -Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. - -```php -$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -switch ($routeInfo[0]) { - case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: - $response = (new \Laminas\Diactoros\Response)->withStatus(405); - $response->getBody()->write('Method not allowed'); - $response = $response->withStatus(405); - break; - case \FastRoute\Dispatcher::FOUND: - $handler = $routeInfo[1]; - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - /** @var \Psr\Http\Message\ResponseInterface $response */ - $response = call_user_func($handler, $request); - break; - case \FastRoute\Dispatcher::NOT_FOUND: - default: - $response = (new \Laminas\Diactoros\Response)->withStatus(404); - $response->getBody()->write('Not Found!'); - break; -} -``` - -In the first part of the code, you are registering the available routes for your application. In the second part, the -dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, -we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. -If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. - -This setup might work for really small applications, but once you start adding a few routes your bootstrap file will -quickly get cluttered. So let's move them out into a separate file. - -Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; - -```php -addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}; -``` - -Now let's rewrite the route dispatcher part to use the `Routes.php` file. - -```php -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); -``` - -This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. - -Of course we now need to add the 'config' folder to the configuration files of our -devhelpers so that they can scan that directory as well. - -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/implementation/16-data-repository/data/pages/07-dispatching-to-a-class.md b/implementation/16-data-repository/data/pages/07-dispatching-to-a-class.md deleted file mode 100644 index 0c961a4..0000000 --- a/implementation/16-data-repository/data/pages/07-dispatching-to-a-class.md +++ /dev/null @@ -1,137 +0,0 @@ -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) - -### Dispatching to a Class - -In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). -MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to -learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) -and the followup posts. - -So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). - -We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other -common names are 'Controllers' or 'Actions'. - -Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. -In there, create a `Hello.php` file. - -```php -getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - } -} -``` - -You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) -that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is -fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement -it in some other parts of our application as well. In order to use that Interface we have to require it with composer: -'composer require psr/http-server-handler'. - -The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. -At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. - -Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: - -```php -return function(\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); - $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); -}; -``` - -Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared -the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing -the response to the one we defined for the second route. - -To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: - -```php -case \FastRoute\Dispatcher::FOUND: - $handler = new $routeInfo[1]; - if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { - throw new \Exception('Invalid Requesthandler'); - } - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - assert($response instanceof \Psr\Http\Message\ResponseInterface) - break; -``` - -So instead of just calling a method you are now instantiating an object and then calling the method on it. - -Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. - -And of course don't forget to commit your changes. - -Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still -generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for -example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still -have our whoopsie error-handler but i like to be more explicit in my control flow. - -In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new -Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and -InternalServerError. All three should extend phps Base Exception class. - -Here is my NotFound.php for example. - -```php - $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - break; - case Dispatcher::METHOD_NOT_ALLOWED: - throw new MethodNotAllowed; - - case Dispatcher::NOT_FOUND: - default: - throw new NotFound; - } -} catch (MethodNotAllowed) { - $response = (new Response)->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = (new Response)->withStatus(404); - $response->getBody()->write('Not Found'); -} catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); -} -``` - -Check if our code still works, try to trigger some errors, run phpstan and the fix command -and don't forget to commit your changes. - -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/implementation/16-data-repository/data/pages/08-inversion-of-control.md b/implementation/16-data-repository/data/pages/08-inversion-of-control.md deleted file mode 100644 index 21f4f23..0000000 --- a/implementation/16-data-repository/data/pages/08-inversion-of-control.md +++ /dev/null @@ -1,54 +0,0 @@ -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) - -### Inversion of Control - -In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we -want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then -we would need to edit every one of our request handlers to call a different constructor of the class. - -The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that -instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done -with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). - -If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is -implemented, it will make sense. - -Change your `Hello` action to the following: - -```php -getAttribute('name', 'Stranger'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: - -```php -$handler = new $className($response); -``` - -Of course we need to also update all the other handlers. - -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/implementation/16-data-repository/data/pages/09-dependency-injector.md b/implementation/16-data-repository/data/pages/09-dependency-injector.md deleted file mode 100644 index 7f7c6a2..0000000 --- a/implementation/16-data-repository/data/pages/09-dependency-injector.md +++ /dev/null @@ -1,213 +0,0 @@ -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) - -### Dependency Injector - -A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when -the class is instantiated. - -Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work -with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look -for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). - -I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) -out of the box. - -After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: - -```php -addDefinitions([ - \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), - \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), -]); - -return $builder->build(); -``` - -In this file we create a containerbuilder, add some definitions to it and return the container. -As the container supports autowiring we only need to define services where we want to use a specific implementation of -an interface. - -In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to -tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second -exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. - -Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), -as we will use that extensively. - -Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally -to create our Handler-Object - -```php -$container = require __DIR__ . '/../config/dependencies.php'; -assert($container instanceof \Psr\Container\ContainerInterface); - -$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); -assert($request instanceof \Psr\Http\Message\ServerRequestInterface); -``` - -The other part that has to be changed is the dispatching of the route. Before you had the following code: - -```php -$className = $routeInfo[1]; -$handler = new $className($response); -assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Change that to the following: - -```php -/** @var RequestHandlerInterface $handler */ -$className = $routeInfo[1]; -$handler = $container->get($className); -assert($handler instanceof RequestHandlerInterface); -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Make sure to use the container fetch the response object in the catch blocks as well: - -```php -} catch (MethodNotAllowed) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(404); - $response->getBody()->write('Not Found'); -} -``` - -Now all your controller constructor dependencies will be automatically resolved with PHP-DI. - -We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons -in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call -`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we -want to work with a different date in a test. - -Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation -that creates us a DateTimeImmutable object with the current date and time. - -src/Service/Time/Now.php: -```php -namespace Lubian\NoFramework\Service\Time; - -interface Now -{ - public function __invoke(): \DateTimeImmutable; -} -``` -src/Service/Time/SystemClockNow.php: -```php -namespace Lubian\NoFramework\Service\Time; - -final class SystemClockNow implements Now -{ - - public function __invoke(): \DateTimeImmutable - { - return new \DateTimeImmutable; - } -} -``` -If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and -update the handle-method to use the new class property: - -```php -getAttribute('name', 'Stranger'); - $nowAsString = ($this->now)()->format('H:i:s'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowAsString); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI -automatically figures out what classes are requested in the constructor and tries to create the objects needed. - -But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred -S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: - -```php - public function __construct( - private ResponseInterface $response, - private Now $now, - ) -``` - -When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation -should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: - -```php -\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), -``` - -we could also use the PHP-DI create method to delegate the object creation to the container implementation: -```php -\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), -``` - -this way the container can try to resolve any dependencies that the class might have internally, but prefer the other -method because we are not depending on this specific dependency injection implementation. - -Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are -requesting the Hello action. - -If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As -we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way -we wont get any warnings for this particular issue, but for any other issues we add to our code. - -Update the phpstan.neon file to include a "baseline" file: - -``` -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` - -if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and -ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just -'baseline' - -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/implementation/16-data-repository/data/pages/10-invoker.md b/implementation/16-data-repository/data/pages/10-invoker.md deleted file mode 100644 index 3033fae..0000000 --- a/implementation/16-data-repository/data/pages/10-invoker.md +++ /dev/null @@ -1,102 +0,0 @@ -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) - -### Invoker - -Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the -one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long -with some services and not the whole Requestobject with all its various properties. - -If we take our Hello action for example we only need a response object, the time service and the 'name' information from -the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named -the class hello and it would be redundant to also call the the method hello. So an updated version of that class could -look like this: - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - string $name = 'Stranger', - ): ResponseInterface - { - $body = $this->response->getBody(); - $nowString = $now->get()->format('H:i:s'); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowString); - return $response - ->withBody($body) - ->withStatus(200); - } -} -``` - -It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short -closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the -rootpath of our application yet. - -```php -$r->addRoute('GET', '/hello[/{name}]', Hello::class); -$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); -$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -``` - -In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the -route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that -class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what -arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router -if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple -callable we would need to manually use reflection as well to resolve all the arguments. - -But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) -which handles all of that for us. - -After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo -switch section in the Bootstrap.php file to use the container->call() method: - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2]; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$response = $container->call($handler, $args); -``` - -Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. - -But by now you should know that I do not like to depend on specific implementations and the call method is not defined in -the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container -or any other implementation. - -Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific -container implementation so we could use it with any container that implements the ContainerInterface. And best of all -the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that -we could implement if we ever want to write our own implementation or we could write an adapter that uses a different -class that solves the same problem. - -But for now we are using the solution provided by PHP-DI. -So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2] ?? []; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$invoker = $container->get(InvokerInterface::class); -assert($invoker instanceof InvokerInterface); -$response = $invoker->call($handler, $args); -assert($response instanceof ResponseInterface); -``` - -Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) -by php, and even some more. - -But let us move on to something more fun and add some templating functionality to our application as we are trying to build -a website in the end. - -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/implementation/16-data-repository/data/pages/11-templating.md b/implementation/16-data-repository/data/pages/11-templating.md deleted file mode 100644 index 7bfe1aa..0000000 --- a/implementation/16-data-repository/data/pages/11-templating.md +++ /dev/null @@ -1,236 +0,0 @@ -[<< previous](10-invoker.md) | [next >>](12-configuration.md) - -### Templating - -A template engine is not necessary with PHP because the language itself can take care of that. But it can make things -like escaping values easier. They also make it easier to draw a clear line between your application logic and the -template files which should only put your variables into the HTML code. - -A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please -also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. -Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. - -For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install -that package before you continue (`composer require mustache/mustache`). - -Another well known alternative would be [Twig](http://twig.sensiolabs.org/). - -Now please go and have a look at the source code of the -[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class -does not implement an interface. - -You could just type hint against the concrete class. But the problem with this approach is that you create tight -coupling. - -In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the -implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want -to add functionality to the engine. You can't do that without going back and changing all your code that is tightly -coupled. - -What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need -another implementation, you just implement that interface in your new class and inject the new class instead. - -Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). -This sounds a lot more complicated than it is, so just follow along. - -First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). -This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. -A class can implement multiple interfaces if necessary. - -So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a -new folder in your `src/` folder with the name `Template` where you can put all the template related things. - -In there create a new interface `Renderer.php` that looks like this: - -```php - $data */ - public function render(string $template, array $data = []) : string; -} -``` - -Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file -`MustacheRenderer.php` with the following content: - -```php -engine->render($template, $data); - } -} -``` - -As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple -and only fulfills the interface. - -Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know -which implementation he has to inject when you hint for the interface. Add this line: - -```php -[ - ... - \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) - ->constructor(new Mustache_Engine), -] -``` - -Now update the Hello.php class to require an implementation of our renderer interface -and use that to render a string using mustache syntax. - - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - Renderer $renderer, - string $name = 'Stranger', - ): ResponseInterface { - $body = $response->getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render( - 'Hello {{name}}, the time is {{now}}!', - $data, - ); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} -``` - -Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. -But what we want is template files, so let's go back and change that. - -To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the -`dependencies.php` file and add the following code: - -```php -[ - ... - Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), - Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), -] -``` - -We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. -Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have -to rename all the template files. - -To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the -dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader -in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object -we can then use it in the factory. - -In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: - -``` -

Hello World

-Hello {{ name }} -``` - -Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` - -Navigate to the hello page in your browser to make sure everything works. - -One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies -file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration -values. - -Lets create a 'Settings' class in our './src' Folder: - -```php -addDefinitions([ - Settings::class => fn () => require __DIR__ '/settings.php', - ResponseInterface::class => create(Response::class), - ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), - Renderer::class => fn (ME $me) => new Mustache($me), - MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), -]); - -return $builder->build(); -``` - - - -And as always, don't forget to commit your changes. - - -[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/implementation/16-data-repository/data/pages/12-configuration.md b/implementation/16-data-repository/data/pages/12-configuration.md deleted file mode 100644 index a44dfd5..0000000 --- a/implementation/16-data-repository/data/pages/12-configuration.md +++ /dev/null @@ -1,201 +0,0 @@ -[<< previous](11-templating.md) | [next >>](13-refactoring.md) - -### Configuration - -In the last chapter we added some more definitions to our dependencies.php in that definitions -we needed to pass quite a few configuration settings and filesystem strings to the constructors -of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. - -As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. - -As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. - -Lets create a 'Settings' class in our './src' Folder: - -```php -filePath; - } -} -``` - -If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files -and craft a settings object from them. - -As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another -factory for our Container because everyone should have a Friend :) - -```php -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn () => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} -``` - -For this to work we need to change our dependencies.php file to just return the array of definitions: -And here we can instantly use the Settings object to create our template engine. - -```php - fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), -]; -``` - -Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: -require __DIR__ . '/../vendor/autoload.php'; - -```php -... -error_reporting(E_ALL); - -$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); -$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); -... -``` - -Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. - -[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/implementation/16-data-repository/data/pages/13-refactoring.md b/implementation/16-data-repository/data/pages/13-refactoring.md deleted file mode 100644 index 067e168..0000000 --- a/implementation/16-data-repository/data/pages/13-refactoring.md +++ /dev/null @@ -1,377 +0,0 @@ -[<< previous](12-configuration.md) | [next >>](14-middleware.md) - -### Refactoring - -By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no -reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. -After all the bootstrap file should just set up the classes needed for the handling logic and execute them. - -At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. -As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the -method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non -static and extracted an interface. - -'./src/Http/Emitter.php' -```php -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - echo $response->getBody(); - } -} -``` -After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following -code in the Bootstrap.php to emit the response: - -```php -/** @var Emitter $emitter */ -$emitter = $container->get(Emitter::class); -$emitter->emit($response); -``` - -If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily -write an adapter that implements your emitter interface and wraps that more advanced emitter - -Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and -calling the routerhandler that in the passes the request to a function and gets the response. - -For this to steps to be seperated we are going to create two more classes: -1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object -2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the - requestobject, fetches the correct object from the container and calls it to create a response. - -Lets create the HandlerInterface first: - -```php -getAttribute($this->routeAttributeName, false); - assert($handler !== false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} - -``` - -We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. -The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler -as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in -the next chapter. - -```php -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} -``` - -Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. -```php -[ - '...', - Emitter::class => fn (BasicEmitter $e) => $e, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, -], -``` - -And then we can update our Bootstrap.php to fetch all the services and let them handle the request. - -```php -... -$routeMiddleWare = $container->get(MiddlewareInterface::class); -assert($routeMiddleWare instanceof MiddlewareInterface); -$handler = $container->get(RoutedRequestHandler::class); -assert($handler instanceof RequestHandlerInterface); -$emitter = $container->get(Emitter::class); -assert($emitter instanceof Emitter); - -$request = $container->get(ServerRequestInterface::class); -assert($request instanceof ServerRequestInterface); - -$response = $routeMiddleWare->process($request, $handler); -$emitter->emit($response); -``` -Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of -code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). - -So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. - -I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class -should require to function properly. - -* A RequestFactory - We want our Kernel to be able to build the request itself -* An Emitter - Without an Emitter we will not be able to send the response to the client -* RouteMiddleware - To decore the request with the correct handler for the requested route -* RequestHandler - To delegate the request to the correct funtion that creates the response - -As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface -to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our -interface: - -```php -factory::fromGlobals(); - } - - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} -``` - -For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: - -```php -routeMiddleware->process($request, $this->handler); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} - -``` - -We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines - -```php -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); -``` - -You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking -at the Whoops output and adding the needed definitions to the dependencies.php file. - -And as always, don't forget to commit your changes. - -[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/implementation/16-data-repository/data/pages/14-middleware.md b/implementation/16-data-repository/data/pages/14-middleware.md deleted file mode 100644 index e698327..0000000 --- a/implementation/16-data-repository/data/pages/14-middleware.md +++ /dev/null @@ -1,298 +0,0 @@ -[<< previous](12-refactoring.md) | [next >>](14-invoker.md) - -### Middleware - -In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain -a bit more about what this interface does and why it is used in many applications. - -The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets -passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all -the middlewars to the client/emitter. - -So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the -response after it gets created by our handlers. - -So lets take a look at the middleware and the requesthandler interfaces - -```php -interface MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; -} - -interface RequestHandlerInterface -{ - public function handle(ServerRequestInterface $request): ResponseInterface; -} -``` - -The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a -requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the -response. - -But the middleware could just ignore the handler and produce a response on its own as the interface just requires us -to produce a response. - -A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users -that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the -database. - -In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates -the request with an 'isAuthenticated' attribute. - -If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that -response is not already cached, than we let the handler create the response and store that in the cache for a few -seconds - -```php -interface CacheInterface -{ - public function get(string $key, callable $resolver, int $ttl): mixed; -} -``` - -The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one -defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses -the callable to produce the value and stores it for the time specified in ttl. - -so lets write our caching middleware: - -```php -final class CachingMiddleware implements MiddlewareInterface -{ - public function __construct(private CacheInterface $cache){} - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { - $key = $request->getUri()->getPath(); - return $this->cache->get($key, fn() => $handler->handle($request), 10); - } - return $handler->handle($request); - } -} -``` - -we can also modify the response after it has been created by our application, for example we could implement a gzip -middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser -know that our application is used to serve dank memes: - -```php -final class DankMemeMiddleware implements MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - $response = $handler->handle($request); - return $response->withAddedHeader('Meme', 'Dank'); - } -} -``` - -but for our application we are going to just add two external middlewares: - -* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. -* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware - -```bash -composer require middlewares/trailing-slash -composer require middlewares/whoops -``` - -The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the -application as well as the middleware stack. - -Our desired request -> response flow looks something like this: - - Client - | ^ - v | - Kernel - | ^ - v | - Whoops Middleware - | ^ - v | - TrailingSlash - | ^ - v | - Routing - | ^ - v | - ContainerResolver - | ^ - v | - Controller/Action - -As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every -middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. - -```php -interface Pipeline -{ - public function dispatch(ServerRequestInterface $request): ResponseInterface; -} -``` - -And our implementation looks something like this: - -```php - $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} -``` - -Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code -that should produce our response. - -In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument -and store that itself as the current tip. - -There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) - -Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline -Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline - -```php -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} -``` - -And configure the container to use the Factory to create the Pipeline: - -```php - ..., - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - ... -``` -And of course a new file called middlewares.php in our config folder: -```php -pipeline->dispatch($request); -} -``` - -Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our -pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a -dependency container, if you want you can do that as well. - -[<< previous](12-refactoring.md) | [next >>](14-invoker.md) diff --git a/implementation/16-data-repository/phpstan-baseline.neon b/implementation/16-data-repository/phpstan-baseline.neon deleted file mode 100644 index 61697a1..0000000 --- a/implementation/16-data-repository/phpstan-baseline.neon +++ /dev/null @@ -1,7 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" - count: 1 - path: src/Http/InvokerRoutedHandler.php - diff --git a/implementation/16-data-repository/phpstan.neon b/implementation/16-data-repository/phpstan.neon deleted file mode 100644 index 2eac45a..0000000 --- a/implementation/16-data-repository/phpstan.neon +++ /dev/null @@ -1,8 +0,0 @@ -includes: - - phpstan-baseline.neon - -parameters: - level: max - paths: - - src - - config \ No newline at end of file diff --git a/implementation/16-data-repository/public/css/spectre-exp.min.css b/implementation/16-data-repository/public/css/spectre-exp.min.css deleted file mode 100644 index d313774..0000000 --- a/implementation/16-data-repository/public/css/spectre-exp.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/implementation/16-data-repository/public/css/spectre-icons.min.css b/implementation/16-data-repository/public/css/spectre-icons.min.css deleted file mode 100644 index 0276f7b..0000000 --- a/implementation/16-data-repository/public/css/spectre-icons.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/implementation/16-data-repository/public/css/spectre.min.css b/implementation/16-data-repository/public/css/spectre.min.css deleted file mode 100644 index 0fe23d9..0000000 --- a/implementation/16-data-repository/public/css/spectre.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/implementation/16-data-repository/public/favicon.ico b/implementation/16-data-repository/public/favicon.ico deleted file mode 100644 index 09499b8b3b3201e0f50088e3ac42e167778d1153..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/16-data-repository/public/index.php b/implementation/16-data-repository/public/index.php deleted file mode 100644 index d93da3a..0000000 --- a/implementation/16-data-repository/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render('hello', $data); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/16-data-repository/src/Action/Other.php b/implementation/16-data-repository/src/Action/Other.php deleted file mode 100644 index da9ceaf..0000000 --- a/implementation/16-data-repository/src/Action/Other.php +++ /dev/null @@ -1,16 +0,0 @@ -parse('This *works* **too!**'); - $response->getBody()->write($html); - return $response->withStatus(200); - } -} diff --git a/implementation/16-data-repository/src/Action/Page.php b/implementation/16-data-repository/src/Action/Page.php deleted file mode 100644 index 4af45f0..0000000 --- a/implementation/16-data-repository/src/Action/Page.php +++ /dev/null @@ -1,60 +0,0 @@ -repo->byName($page); - - // fix the next and previous buttons to work with our routing - $content = preg_replace('/\(\d\d-/m', '(', $page->content); - assert(is_string($content)); - $content = str_replace('.md)', ')', $content); - - $data = [ - 'title' => $page->title, - 'content' => $this->parser->parse($content), - ]; - - $html = $this->renderer->render('page/show', $data); - $this->response->getBody()->write($html); - return $this->response; - } - - public function list(): ResponseInterface - { - $pages = array_map(function (MarkdownPage $page) { - return [ - 'id' => $page->id, - 'title' => $page->content, - ]; - }, $this->repo->all()); - - $html = $this->renderer->render('page/list', ['pages' => $pages]); - $this->response->getBody()->write($html); - return $this->response; - } -} diff --git a/implementation/16-data-repository/src/Bootstrap.php b/implementation/16-data-repository/src/Bootstrap.php deleted file mode 100644 index 3abc2e5..0000000 --- a/implementation/16-data-repository/src/Bootstrap.php +++ /dev/null @@ -1,40 +0,0 @@ -getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); diff --git a/implementation/16-data-repository/src/Exception/InternalServerError.php b/implementation/16-data-repository/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/implementation/16-data-repository/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ -factory::fromGlobals(); - } - - /** - * @param UriInterface|string $uri - * @param array $serverParams - */ - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} diff --git a/implementation/16-data-repository/src/Factory/DoctrineEm.php b/implementation/16-data-repository/src/Factory/DoctrineEm.php deleted file mode 100644 index b0be39b..0000000 --- a/implementation/16-data-repository/src/Factory/DoctrineEm.php +++ /dev/null @@ -1,32 +0,0 @@ -settings->doctrine['devMode']); - - $config->setMetadataDriverImpl( - new AttributeDriver( - $this->settings->doctrine['metadataDirs'] - ) - ); - - return EntityManager::create( - $this->settings->connection, - $config, - ); - } -} diff --git a/implementation/16-data-repository/src/Factory/FileSystemSettingsProvider.php b/implementation/16-data-repository/src/Factory/FileSystemSettingsProvider.php deleted file mode 100644 index f071078..0000000 --- a/implementation/16-data-repository/src/Factory/FileSystemSettingsProvider.php +++ /dev/null @@ -1,22 +0,0 @@ -filePath; - assert($settings instanceof Settings); - return $settings; - } -} diff --git a/implementation/16-data-repository/src/Factory/PipelineProvider.php b/implementation/16-data-repository/src/Factory/PipelineProvider.php deleted file mode 100644 index 77738f8..0000000 --- a/implementation/16-data-repository/src/Factory/PipelineProvider.php +++ /dev/null @@ -1,25 +0,0 @@ -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} diff --git a/implementation/16-data-repository/src/Factory/RequestFactory.php b/implementation/16-data-repository/src/Factory/RequestFactory.php deleted file mode 100644 index 2b17abc..0000000 --- a/implementation/16-data-repository/src/Factory/RequestFactory.php +++ /dev/null @@ -1,11 +0,0 @@ -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = $settings; - $builder->addDefinitions($dependencies); - // $builder->enableCompilation('/tmp'); - return $builder->build(); - } -} diff --git a/implementation/16-data-repository/src/Factory/SettingsProvider.php b/implementation/16-data-repository/src/Factory/SettingsProvider.php deleted file mode 100644 index ce1c5f0..0000000 --- a/implementation/16-data-repository/src/Factory/SettingsProvider.php +++ /dev/null @@ -1,10 +0,0 @@ -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - - echo $response->getBody(); - } -} diff --git a/implementation/16-data-repository/src/Http/ContainerPipeline.php b/implementation/16-data-repository/src/Http/ContainerPipeline.php deleted file mode 100644 index 816cedd..0000000 --- a/implementation/16-data-repository/src/Http/ContainerPipeline.php +++ /dev/null @@ -1,82 +0,0 @@ - $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} diff --git a/implementation/16-data-repository/src/Http/Emitter.php b/implementation/16-data-repository/src/Http/Emitter.php deleted file mode 100644 index ce4c035..0000000 --- a/implementation/16-data-repository/src/Http/Emitter.php +++ /dev/null @@ -1,10 +0,0 @@ -getAttribute($this->routeAttributeName, false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} diff --git a/implementation/16-data-repository/src/Http/Pipeline.php b/implementation/16-data-repository/src/Http/Pipeline.php deleted file mode 100644 index 1a9dcda..0000000 --- a/implementation/16-data-repository/src/Http/Pipeline.php +++ /dev/null @@ -1,11 +0,0 @@ -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} diff --git a/implementation/16-data-repository/src/Http/RoutedRequestHandler.php b/implementation/16-data-repository/src/Http/RoutedRequestHandler.php deleted file mode 100644 index a7407c9..0000000 --- a/implementation/16-data-repository/src/Http/RoutedRequestHandler.php +++ /dev/null @@ -1,10 +0,0 @@ -pipeline->dispatch($request); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} diff --git a/implementation/16-data-repository/src/Model/MarkdownPage.php b/implementation/16-data-repository/src/Model/MarkdownPage.php deleted file mode 100644 index df244fd..0000000 --- a/implementation/16-data-repository/src/Model/MarkdownPage.php +++ /dev/null @@ -1,13 +0,0 @@ -dataPath . '*.md'); - if ($files === false) { - throw new InternalServerError('cannot read pages'); - } - return array_map(function (string $filename) { - $content = file_get_contents($filename); - if ($content === false) { - throw new InternalServerError('cannot read pages'); - } - $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); - return new MarkdownPage( - (int) substr($idAndTitle, 0, 2), - substr($idAndTitle, 3), - $content - ); - }, $files); - } - - public function byName(string $name): MarkdownPage - { - $pages = array_values( - array_filter( - $this->all(), - fn (MarkdownPage $p) => $p->title === $name, - ) - ); - - if (count($pages) !== 1) { - throw new NotFound; - } - - return $pages[0]; - } -} diff --git a/implementation/16-data-repository/src/Repository/MarkdownPageRepo.php b/implementation/16-data-repository/src/Repository/MarkdownPageRepo.php deleted file mode 100644 index 0792d32..0000000 --- a/implementation/16-data-repository/src/Repository/MarkdownPageRepo.php +++ /dev/null @@ -1,15 +0,0 @@ -engine->render($template, $data); - } -} diff --git a/implementation/16-data-repository/src/Template/ParsedownParser.php b/implementation/16-data-repository/src/Template/ParsedownParser.php deleted file mode 100644 index 2ffd287..0000000 --- a/implementation/16-data-repository/src/Template/ParsedownParser.php +++ /dev/null @@ -1,17 +0,0 @@ -parser->parse($markdown); - } -} diff --git a/implementation/16-data-repository/src/Template/Renderer.php b/implementation/16-data-repository/src/Template/Renderer.php deleted file mode 100644 index ff916ed..0000000 --- a/implementation/16-data-repository/src/Template/Renderer.php +++ /dev/null @@ -1,11 +0,0 @@ - $data - */ - public function render(string $template, array $data = []): string; -} diff --git a/implementation/16-data-repository/templates/hello.html b/implementation/16-data-repository/templates/hello.html deleted file mode 100644 index 15a4cd2..0000000 --- a/implementation/16-data-repository/templates/hello.html +++ /dev/null @@ -1,6 +0,0 @@ -{{> partials/head }} -
-

Hello {{name}}

-

The time is {{now}}

-
-{{> partials/foot }} diff --git a/implementation/16-data-repository/templates/page.html b/implementation/16-data-repository/templates/page.html deleted file mode 100644 index c3c5284..0000000 --- a/implementation/16-data-repository/templates/page.html +++ /dev/null @@ -1,5 +0,0 @@ -{{> partials/head }} -
- {{{content}}} -
-{{> partials/foot }} diff --git a/implementation/16-data-repository/templates/page/list.html b/implementation/16-data-repository/templates/page/list.html deleted file mode 100644 index bf42348..0000000 --- a/implementation/16-data-repository/templates/page/list.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Pages - - - -
- -
- - \ No newline at end of file diff --git a/implementation/16-data-repository/templates/page/show.html b/implementation/16-data-repository/templates/page/show.html deleted file mode 100644 index abe295e..0000000 --- a/implementation/16-data-repository/templates/page/show.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - {{title}} - - - - - - -
- {{{content}}} -
- - \ No newline at end of file diff --git a/implementation/16-data-repository/templates/pagelist.html b/implementation/16-data-repository/templates/pagelist.html deleted file mode 100644 index 538e2c4..0000000 --- a/implementation/16-data-repository/templates/pagelist.html +++ /dev/null @@ -1,11 +0,0 @@ -{{> partials/head }} -
- -
-{{> partials/foot }} diff --git a/implementation/16-data-repository/templates/partials/foot.html b/implementation/16-data-repository/templates/partials/foot.html deleted file mode 100644 index 17c7245..0000000 --- a/implementation/16-data-repository/templates/partials/foot.html +++ /dev/null @@ -1,3 +0,0 @@ -
- - \ No newline at end of file diff --git a/implementation/16-data-repository/templates/partials/head.html b/implementation/16-data-repository/templates/partials/head.html deleted file mode 100644 index 421d387..0000000 --- a/implementation/16-data-repository/templates/partials/head.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - No Framework: {{title}} - - - - - -
diff --git a/implementation/18-caching/.php-cs-fixer.php b/implementation/18-caching/.php-cs-fixer.php deleted file mode 100644 index 705a7d7..0000000 --- a/implementation/18-caching/.php-cs-fixer.php +++ /dev/null @@ -1,38 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/config' - ]) - ); \ No newline at end of file diff --git a/implementation/18-caching/.phpcs.xml.dist b/implementation/18-caching/.phpcs.xml.dist deleted file mode 100644 index 3b433f6..0000000 --- a/implementation/18-caching/.phpcs.xml.dist +++ /dev/null @@ -1,9 +0,0 @@ - - - - - src - config - - - \ No newline at end of file diff --git a/implementation/18-caching/cli-config.php b/implementation/18-caching/cli-config.php deleted file mode 100644 index fbc6598..0000000 --- a/implementation/18-caching/cli-config.php +++ /dev/null @@ -1,13 +0,0 @@ -getContainer(); - -return ConsoleRunner::createHelperSet($container->get(EntityManagerInterface::class)); diff --git a/implementation/18-caching/composer.json b/implementation/18-caching/composer.json deleted file mode 100644 index 29695da..0000000 --- a/implementation/18-caching/composer.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.8", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "php-di/php-di": "^6.3", - "mustache/mustache": "^2.14", - "psr/http-server-middleware": "^1.0", - "middlewares/trailing-slash": "^2.0", - "middlewares/whoops": "^2.0", - "erusev/parsedown": "^1.7", - "league/commonmark": "^2.2", - "ext-apcu": "*", - "ext-zend-opcache": "*" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubian", - "email": "test@example.com" - } - ], - "require-dev": { - "phpstan/phpstan": "^1.5", - "php-cs-fixer/shim": "^3.8", - "symfony/var-dumper": "^6.0", - "squizlabs/php_codesniffer": "^3.6", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1", - "thecodingmachine/phpstan-strict-rules": "^1.0", - "mnapoli/hard-mode": "^0.3.0" - }, - "config": { - "optimize-autoloader": true, - "allow-plugins": { - "phpstan/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "serve": [ - "Composer\\Config::disableProcessTimeout", - "php -S 0.0.0.0:1234 -t public" - ], - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" - } -} diff --git a/implementation/18-caching/composer.lock b/implementation/18-caching/composer.lock deleted file mode 100644 index 40cd7d3..0000000 --- a/implementation/18-caching/composer.lock +++ /dev/null @@ -1,2440 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "9a29468fd456190a9fbcff98ed42d862", - "packages": [ - { - "name": "dflydev/dot-access-data", - "version": "v3.0.1", - "source": { - "type": "git", - "url": "https://github.com/dflydev/dflydev-dot-access-data.git", - "reference": "0992cc19268b259a39e86f296da5f0677841f42c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", - "reference": "0992cc19268b259a39e86f296da5f0677841f42c", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.42", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", - "scrutinizer/ocular": "1.6.0", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^3.14" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Dflydev\\DotAccessData\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dragonfly Development Inc.", - "email": "info@dflydev.com", - "homepage": "http://dflydev.com" - }, - { - "name": "Beau Simensen", - "email": "beau@dflydev.com", - "homepage": "http://beausimensen.com" - }, - { - "name": "Carlos Frutos", - "email": "carlos@kiwing.it", - "homepage": "https://github.com/cfrutos" - }, - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com" - } - ], - "description": "Given a deep data structure, access data by dot notation.", - "homepage": "https://github.com/dflydev/dflydev-dot-access-data", - "keywords": [ - "access", - "data", - "dot", - "notation" - ], - "support": { - "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", - "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" - }, - "time": "2021-08-13T13:06:58+00:00" - }, - { - "name": "erusev/parsedown", - "version": "1.7.4", - "source": { - "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35" - }, - "type": "library", - "autoload": { - "psr-0": { - "Parsedown": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com" - } - ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org", - "keywords": [ - "markdown", - "parser" - ], - "support": { - "issues": "https://github.com/erusev/parsedown/issues", - "source": "https://github.com/erusev/parsedown/tree/1.7.x" - }, - "time": "2019-12-30T22:54:17+00:00" - }, - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.9.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/954e2dcfb1607681be44599faac10fc63bb6925a", - "reference": "954e2dcfb1607681be44599faac10fc63bb6925a", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2022-03-29T20:12:16+00:00" - }, - { - "name": "league/commonmark", - "version": "2.2.3", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/commonmark.git", - "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", - "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "league/config": "^1.1.1", - "php": "^7.4 || ^8.0", - "psr/event-dispatcher": "^1.0", - "symfony/deprecation-contracts": "^2.1 || ^3.0", - "symfony/polyfill-php80": "^1.15" - }, - "require-dev": { - "cebe/markdown": "^1.0", - "commonmark/cmark": "0.30.0", - "commonmark/commonmark.js": "0.30.0", - "composer/package-versions-deprecated": "^1.8", - "erusev/parsedown": "^1.0", - "ext-json": "*", - "github/gfm": "0.29.0", - "michelf/php-markdown": "^1.4", - "phpstan/phpstan": "^0.12.88 || ^1.0.0", - "phpunit/phpunit": "^9.5.5", - "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", - "unleashedtech/php-coding-standard": "^3.1", - "vimeo/psalm": "^4.7.3" - }, - "suggest": { - "symfony/yaml": "v2.3+ required if using the Front Matter extension" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.3-dev" - } - }, - "autoload": { - "psr-4": { - "League\\CommonMark\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com", - "role": "Lead Developer" - } - ], - "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", - "homepage": "https://commonmark.thephpleague.com", - "keywords": [ - "commonmark", - "flavored", - "gfm", - "github", - "github-flavored", - "markdown", - "md", - "parser" - ], - "support": { - "docs": "https://commonmark.thephpleague.com/", - "forum": "https://github.com/thephpleague/commonmark/discussions", - "issues": "https://github.com/thephpleague/commonmark/issues", - "rss": "https://github.com/thephpleague/commonmark/releases.atom", - "source": "https://github.com/thephpleague/commonmark" - }, - "funding": [ - { - "url": "https://www.colinodell.com/sponsor", - "type": "custom" - }, - { - "url": "https://www.paypal.me/colinpodell/10.00", - "type": "custom" - }, - { - "url": "https://github.com/colinodell", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/commonmark", - "type": "tidelift" - } - ], - "time": "2022-02-26T21:24:45+00:00" - }, - { - "name": "league/config", - "version": "v1.1.1", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/config.git", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "shasum": "" - }, - "require": { - "dflydev/dot-access-data": "^3.0.1", - "nette/schema": "^1.2", - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.90", - "phpunit/phpunit": "^9.5.5", - "scrutinizer/ocular": "^1.8.1", - "unleashedtech/php-coding-standard": "^3.1", - "vimeo/psalm": "^4.7.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.2-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Config\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com", - "role": "Lead Developer" - } - ], - "description": "Define configuration arrays with strict schemas and access values with dot notation", - "homepage": "https://config.thephpleague.com", - "keywords": [ - "array", - "config", - "configuration", - "dot", - "dot-access", - "nested", - "schema" - ], - "support": { - "docs": "https://config.thephpleague.com/", - "issues": "https://github.com/thephpleague/config/issues", - "rss": "https://github.com/thephpleague/config/releases.atom", - "source": "https://github.com/thephpleague/config" - }, - "funding": [ - { - "url": "https://www.colinodell.com/sponsor", - "type": "custom" - }, - { - "url": "https://www.paypal.me/colinpodell/10.00", - "type": "custom" - }, - { - "url": "https://github.com/colinodell", - "type": "github" - } - ], - "time": "2021-08-14T12:15:32+00:00" - }, - { - "name": "middlewares/trailing-slash", - "version": "v2.0.1", - "source": { - "type": "git", - "url": "https://github.com/middlewares/trailing-slash.git", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/trailing-slash/zipball/1bedcedbc89be78595c5a7a86776fe5ed003e819", - "reference": "1bedcedbc89be78595c5a7a86776fe5ed003e819", - "shasum": "" - }, - "require": { - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to normalize the trailing slash of the uri path", - "homepage": "https://github.com/middlewares/trailing-slash", - "keywords": [ - "http", - "middleware", - "normalize", - "path", - "psr-15", - "psr-7", - "slash" - ], - "support": { - "issues": "https://github.com/middlewares/trailing-slash/issues", - "source": "https://github.com/middlewares/trailing-slash/tree/v2.0.1" - }, - "time": "2020-12-02T00:06:55+00:00" - }, - { - "name": "middlewares/utils", - "version": "v3.3.0", - "source": { - "type": "git", - "url": "https://github.com/middlewares/utils.git", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", - "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^v2.16", - "guzzlehttp/psr7": "^2.0", - "laminas/laminas-diactoros": "^2.4", - "nyholm/psr7": "^1.0", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "slim/psr7": "^1.4", - "squizlabs/php_codesniffer": "^3.5", - "sunrise/http-message": "^1.0", - "sunrise/http-server-request": "^1.0", - "sunrise/stream": "^1.0.15", - "sunrise/uri": "^1.0.15" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\Utils\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Common utils for PSR-15 middleware packages", - "homepage": "https://github.com/middlewares/utils", - "keywords": [ - "PSR-11", - "http", - "middleware", - "psr-15", - "psr-17", - "psr-7" - ], - "support": { - "issues": "https://github.com/middlewares/utils/issues", - "source": "https://github.com/middlewares/utils/tree/v3.3.0" - }, - "time": "2021-07-04T17:56:23+00:00" - }, - { - "name": "middlewares/whoops", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/middlewares/whoops.git", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/middlewares/whoops/zipball/bf0141230ac26814e16f416a75a9596206aefa5f", - "reference": "bf0141230ac26814e16f416a75a9596206aefa5f", - "shasum": "" - }, - "require": { - "filp/whoops": "^2.5", - "middlewares/utils": "^3.0", - "php": "^7.2 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "psr/http-server-middleware": "^1.0" - }, - "require-dev": { - "eloquent/phony-phpunit": "^5.0 || ^7.0", - "friendsofphp/php-cs-fixer": "^2.0", - "laminas/laminas-diactoros": "^2.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8|^9", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Middlewares\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Middleware to use Whoops as error handler", - "homepage": "https://github.com/middlewares/whoops", - "keywords": [ - "error", - "http", - "middleware", - "psr-15", - "psr-7", - "server", - "whoops" - ], - "support": { - "issues": "https://github.com/middlewares/whoops/issues", - "source": "https://github.com/middlewares/whoops/tree/v2.0.2" - }, - "time": "2022-01-27T20:31:30+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nette/schema", - "version": "v1.2.2", - "source": { - "type": "git", - "url": "https://github.com/nette/schema.git", - "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", - "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", - "shasum": "" - }, - "require": { - "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": ">=7.1 <8.2" - }, - "require-dev": { - "nette/tester": "^2.3 || ^2.4", - "phpstan/phpstan-nette": "^0.12", - "tracy/tracy": "^2.7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "📐 Nette Schema: validating data structures against a given Schema.", - "homepage": "https://nette.org", - "keywords": [ - "config", - "nette" - ], - "support": { - "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.2" - }, - "time": "2021-10-15T11:40:02+00:00" - }, - { - "name": "nette/utils", - "version": "v3.2.7", - "source": { - "type": "git", - "url": "https://github.com/nette/utils.git", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", - "shasum": "" - }, - "require": { - "php": ">=7.2 <8.2" - }, - "conflict": { - "nette/di": "<3.0.6" - }, - "require-dev": { - "nette/tester": "~2.0", - "phpstan/phpstan": "^1.0", - "tracy/tracy": "^2.3" - }, - "suggest": { - "ext-gd": "to use Image", - "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", - "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", - "ext-json": "to use Nette\\Utils\\Json", - "ext-mbstring": "to use Strings::lower() etc...", - "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", - "ext-xml": "to use Strings::length() etc. when mbstring is not available" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", - "homepage": "https://nette.org", - "keywords": [ - "array", - "core", - "datetime", - "images", - "json", - "nette", - "paginator", - "password", - "slugify", - "string", - "unicode", - "utf-8", - "utility", - "validation" - ], - "support": { - "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.2.7" - }, - "time": "2022-01-24T11:29:14+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "opis/closure", - "version": "3.6.3", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/b8126d066ce144765300ee0ab040c1ed6c9ef588", - "reference": "b8126d066ce144765300ee0ab040c1ed6c9ef588", - "shasum": "" - }, - "require": { - "opis/closure": "^3.5.5", - "php": ">=7.2.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.0.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.5|^9.0" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2021-09-02T09:49:58+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/http-server-middleware", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-middleware.git", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0", - "psr/http-server-handler": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side middleware", - "keywords": [ - "http", - "http-interop", - "middleware", - "psr", - "psr-15", - "psr-7", - "request", - "response" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-middleware/issues", - "source": "https://github.com/php-fig/http-server-middleware/tree/master" - }, - "time": "2018-10-30T17:12:04+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.0.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "shasum": "" - }, - "require": { - "php": ">=8.0.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:55:41+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-04T08:16:47+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "doctrine/coding-standard", - "version": "8.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/coding-standard.git", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "reference": "f595b060799c1a0d76ead16981804eaa0bbcd8d6", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "slevomat/coding-standard": "^6.4.1", - "squizlabs/php_codesniffer": "^3.5.8" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Steve Müller", - "email": "st.mueller@dzh-online.de" - } - ], - "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", - "keywords": [ - "checks", - "code", - "coding", - "cs", - "doctrine", - "rules", - "sniffer", - "sniffs", - "standard", - "style" - ], - "support": { - "issues": "https://github.com/doctrine/coding-standard/issues", - "source": "https://github.com/doctrine/coding-standard/tree/8.2.1" - }, - "time": "2021-04-03T10:54:55+00:00" - }, - { - "name": "mnapoli/hard-mode", - "version": "0.3.0", - "source": { - "type": "git", - "url": "https://github.com/mnapoli/hard-mode.git", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mnapoli/hard-mode/zipball/9fe24485a079ae8a568113a2d582270cd0265fa2", - "reference": "9fe24485a079ae8a568113a2d582270cd0265fa2", - "shasum": "" - }, - "require": { - "doctrine/coding-standard": "^8.0" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Hard mode for PHP", - "support": { - "issues": "https://github.com/mnapoli/hard-mode/issues", - "source": "https://github.com/mnapoli/hard-mode/tree/0.3.0" - }, - "time": "2020-10-12T07:54:37+00:00" - }, - { - "name": "php-cs-fixer/shim", - "version": "v3.8.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/d0085a8083140e5203b1ce43add92f894b247e44", - "reference": "d0085a8083140e5203b1ce43add92f894b247e44", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.8.0" - }, - "time": "2022-03-18T17:23:40+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.9", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.26", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/master" - }, - "time": "2020-08-03T20:32:43+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.5.4", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", - "reference": "bbf68cae24f6dc023c607ea0f87da55dd9d55c2b", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.5.4" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-04-03T12:39:00+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717", - "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^1.2.0" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0" - }, - "time": "2021-11-18T09:30:29+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "6.4.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpstan/phpstan": "0.12.48", - "phpstan/phpstan-deprecation-rules": "0.12.5", - "phpstan/phpstan-phpunit": "0.12.16", - "phpstan/phpstan-strict-rules": "0.12.5", - "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2020-10-05T12:39:37+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38358405ae948963c50a3aae3dfea598223ba15e", - "reference": "38358405ae948963c50a3aae3dfea598223ba15e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-03-02T12:58:14+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^8.1", - "ext-apcu": "*", - "ext-zend-opcache": "*" - }, - "platform-dev": [], - "plugin-api-version": "2.3.0" -} diff --git a/implementation/18-caching/config/dependencies.php b/implementation/18-caching/config/dependencies.php deleted file mode 100644 index df815c6..0000000 --- a/implementation/18-caching/config/dependencies.php +++ /dev/null @@ -1,58 +0,0 @@ - fn (SystemClockNow $n) => $n, - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, - Emitter::class => fn (BasicEmitter $e) => $e, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf, - MarkdownParser::class => fn (ParsedownParser $p) => $p, - MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, - EasyCache::class => fn (ApcuCache $c) => $c, - CachedMarkdownPageRepo::class => fn (EasyCache $c, FileSystemMarkdownPageRepo $r) => new CachedMarkdownPageRepo($c, $r), - - - // Factories - ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(), - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), - Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), -]; diff --git a/implementation/18-caching/config/middlewares.php b/implementation/18-caching/config/middlewares.php deleted file mode 100644 index ab662be..0000000 --- a/implementation/18-caching/config/middlewares.php +++ /dev/null @@ -1,13 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/page', [Page::class, 'list']); - $r->addRoute('GET', '/page/{page}', [Page::class, 'show']); - $r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']); - $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -}; diff --git a/implementation/18-caching/config/settings.php b/implementation/18-caching/config/settings.php deleted file mode 100644 index c654565..0000000 --- a/implementation/18-caching/config/settings.php +++ /dev/null @@ -1,12 +0,0 @@ ->](02-composer.md) - -### Front Controller - -A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. - -To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. - -A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. - -The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. - -But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. - -So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. - -Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: - -```php ->](02-composer.md) diff --git a/implementation/18-caching/data/pages/02-composer.md b/implementation/18-caching/data/pages/02-composer.md deleted file mode 100644 index a25a4a8..0000000 --- a/implementation/18-caching/data/pages/02-composer.md +++ /dev/null @@ -1,75 +0,0 @@ -[<< previous](01-front-controller.md) | [next >>](03-error-handler.md) - -### Composer - -[Composer](https://getcomposer.org/) is a dependency manager for PHP. - -Just because you are not using a framework does not mean you will have to reinvent the wheel every time you want to do -something. With Composer, you can install third-party libraries for your application. - -If you don't have Composer installed already, head over to the website and install it. You can find Composer packages -for your project on [Packagist](https://packagist.org/). - -Create a new file in your project root folder called `composer.json`. This is the Composer configuration file that will -be used to configure your project and its dependencies. It must be valid JSON or Composer will fail. - -Add the following content to the file: - -```json -{ - "name": "lubian/no-framework", - "require": { - "php": "^8.1" - }, - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "lubiana", - "email": "lubiana@hannover.ccc.de" - } - ] -} -``` - -In the autoload part you can see that I am using the `Lubian\NoFramework` namespace for the project. You can use -whatever fits your project there, but from now on I will always use the `Lubian\NoFramework` namespace in my examples. -Just replace it with your namespace in your own code. - -I have also defined, that all my code and classes in the 'Lubian\NoFramework' namespace lives under the './src' folder. - -As the Bootstrap.php file is placed in that directory we should -add the namespace to the File as well. Here is my current Bootstrap.php -as a reference: - -```php ->](03-error-handler.md) diff --git a/implementation/18-caching/data/pages/03-error-handler.md b/implementation/18-caching/data/pages/03-error-handler.md deleted file mode 100644 index 60465d0..0000000 --- a/implementation/18-caching/data/pages/03-error-handler.md +++ /dev/null @@ -1,79 +0,0 @@ -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) - -### Error Handler - -An error handler allows you to customize what happens if your code results in an error. - -A nice error page with a lot of information for debugging goes a long way during development. So the first package -for your application will take care of that. - -I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. -If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, -you have total control over your project. - -An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) - -To install a new package, open up your `composer.json` and add the package to the require part. It should now look -like this: - -```php -"require": { - "php": ">=8.1.0", - "filp/whoops": "^2.14" -}, -``` - -Now run `composer update` in your console and it will be installed. - -Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, -i that case composer automatically installs the package and updates your composer.json-file. - -But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, -ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you -only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. - -**Important:** Never show any errors in your production environment. A stack trace or even just a simple error message -can help someone to gain access to your system. Always show a user friendly error page instead and send an email to -yourself, write to a log or something similar. So only you can see the errors in the production environment. - -For development that does not make sense though -- you want a nice error page. The solution is to have an environment -switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in -case no environment has been set. - -Then after the error handler registration, throw an `Exception` to test if everything is working correctly. -Your `Bootstrap.php` should now look similar to this: - -```php -pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (\Throwable $e) { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -throw new \Exception("Ooooopsie"); - -``` - -You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until -you get it working. Now would also be a good time for another commit. - - -[<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/implementation/18-caching/data/pages/04-development-helpers.md b/implementation/18-caching/data/pages/04-development-helpers.md deleted file mode 100644 index 74f913c..0000000 --- a/implementation/18-caching/data/pages/04-development-helpers.md +++ /dev/null @@ -1,260 +0,0 @@ -[<< previous](03-error-handler.md) | [next >>](05-http.md) - -### Development Helpers - -I have added some more helpers to my composer.json that help me with development. As these are scripts and programms -used only for development they should not be used in a production environment. Composer has a specific sections in its -file called "dev-dependencies", everything that is required in this section does not get installen in production. - -Let's install our dev-helpers and i will explain them one by one: -`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` - -#### Static Code Analysis with phpstan - -Phpstan is a great little tool, that tries to understand your code and checks if you are making any grave mistakes or -create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements -that are not (or not always) available, if-statements that always are true and a lot of other stuff. - -A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. - -```php -/** - * @param \DateTime $date - * @return void - */ -function printDate($date) { - $date->format('Y-m-d H:i:s'); -} - -printDate('now'); -``` -if we run phpstan with the command `./vendor/bin/phpstan analyse --level 9 ./src/` - -It firstly tells us that calling "format" on a DateTime-Object without outputting or returning the function result has -no use, and secondly, that we are calling the function with a string instead of a datetime object. - -```shell -1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - ------ --------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ --------------------------------------------------------------------------------------------- -30 Call to method DateTime::format() on a separate line has no effect. -33 Parameter #1 $date of function Lubian\NoFramework\printDate expects DateTime, string given. - ------ --------------------------------------------------------------------------------------------- -``` - -The second error is something that "declare strict-types" already catches for us, but the first error is something that -we usually would not discover easily without speccially looking for this errortype. - -We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and -path everytime we want to check our code for errors: - -```yaml -parameters: - level: max - paths: - - src -``` -now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project - -With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us -some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. - -```shell -composer require --dev phpstan/extension-installer -composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-strict-rules -``` - -During the first install you need to allow the extension installer to actually install the extension. The second command -installs some more strict rulesets and activates them in phpstan. - -If we now rerun phpstan it already tells us about some errors we have made: - -``` - ------ ----------------------------------------------------------------------------------------------- -Line Bootstrap.php - ------ ----------------------------------------------------------------------------------------------- -10 Short ternary operator is not allowed. Use null coalesce operator if applicable or consider - using long ternary. -25 Do not throw the \Exception base class. Instead, extend the \Exception base class. More info: - http://bit.ly/subtypeexception -26 Unreachable statement - code above always terminates. - ------ ----------------------------------------------------------------------------------------------- -``` - -The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove -that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific -problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working -on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages -everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we -are adding to our code. - -In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the -phpstan.neon file and run phpstan with the -'--generate-baseline' option: - -```yaml -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` -```shell -[vagrant@archlinux app]$ ./vendor/bin/phpstan analyze --generate-baseline -Note: Using configuration file /home/vagrant/app/phpstan.neon. - 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% - - - - [OK] Baseline generated with 1 error. - - -``` - -you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) - -#### PHP-CS-Fixer - -Another great tool is the php-cs-fixer, which just applies a specific style to your code. - -when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current -directory. - -You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) - -personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup -a configuration file that i use in all my projects .php-cs-fixer.php: - -```php -setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - ]) - ); -``` - -#### PHP Codesniffer - -The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra -rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. - -it provides the phpcs command to check for violations and the phpcbf command to actually fix most of the violations. - -Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have -guessed we are adding some extra rules. - -Lets install the ruleset with composer -```shell -composer require --dev mnapoli/hard-mode -``` - -and add a configuration file to actually use it '.phpcs.xml.dist' -```xml - - - - - src - - - -``` - -running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. - -``` -[vagrant@archlinux app]$ ./vendor/bin/phpcs - -FILE: src/Bootstrap.php ----------------------------------------------------------------------------------------------------- -FOUND 4 ERRORS AFFECTING 4 LINES ----------------------------------------------------------------------------------------------------- - 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. - 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead ----------------------------------------------------------------------------------------------------- -PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY ----------------------------------------------------------------------------------------------------- - -Time: 639ms; Memory: 10MB -``` - -You can then use `./vendor/bin/phpcbf` to try to fix them - - -#### Symfony Var-Dumper - -another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small -functions. - -dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects -or arrays. - -dd() on the other hand is a function that dumps its parameters and then exits the php-script. - -you could just write dd($whoops) somewhere in your bootstrap.php to check how the output looks. - -#### Composer scripts - -now we have a few commands that are available on the command line. i personally do not like to type complex commands -with lots of parameters by hand all the time, so i added a few lines to my composer.json: - -```json -"scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" -}, -``` - -that way i can just type "composer" followed by the command name in the root of my project. if i want to start the -php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the -time. - -You could also configure PhpStorm to automatically run these commands in the background and highlight the violations -directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my -flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. - -My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before -commiting and pushing the code. - -[<< previous](03-error-handler.md) | [next >>](05-http.md) diff --git a/implementation/18-caching/data/pages/05-http.md b/implementation/18-caching/data/pages/05-http.md deleted file mode 100644 index 6166214..0000000 --- a/implementation/18-caching/data/pages/05-http.md +++ /dev/null @@ -1,124 +0,0 @@ -[<< previous](04-development-helpers.md) | [next >>](06-router.md) - -### HTTP - -PHP already has a few things built in to make working with HTTP easier. For example there are the -[superglobals](http://php.net/manual/en/language.variables.superglobals.php) that contain the request information. - -These are good if you just want to get a small script up and running, something that won't be hard to maintain. However, -if you want to write clean, maintainable, [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) code, -then you will want a class with a nice object-oriented interface that you can use in your application instead. - -Fortunately for us there has been a standard developed in the PHP-Community that is adopted by several Frameworks. The -standard is called [PSR-7](https://www.php-fig.org/psr/psr-7/) and has several interfaces defined that a lot of php -projects implement. This makes it easier for us to use modules developed for other frameworks in our projects. - -As this is a widely adopted standard there are already several implementations available for us to use. I will choose -the laminas/laminas-diactoros package as i am an old time fan of the laminas (previously zend) project. - -Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a -[lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. - -Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use -that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding -the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). - - -to install the laminas psr-packages just type `composer require laminas/laminas-diactoros` into your console and hit -enter - -Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): - -```php -$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); -$response = new \Laminas\Diactoros\Response; -$response->getBody()->write('Hello World! '); -$response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); -``` - -This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. - -In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the -write()-Method on that object. - - -To actually send something back, you will also need to add the following snippet at the end of your `Bootstrap.php` file: - -```php -echo $response->getBody(); -``` - -This will send the response data to the browser. If you don't do this, nothing happens as the `Response` object only -stores data. - -You can play around with the other methods of the Request object and take a look at its content with the dd() function. - -```php -dd($response) -``` - -Something you have to keep in mind is that the Response and Request objects are Immutable which means that they cannot -be changed after creation. Whenever you want to modify a property you have to call one of the "with" functions, which -creates a copy of the request object with the changed property and returns that clone: - -```php -$response = $response->withStatus(200); -$response = $response->withAddedHeader('Content-type', 'application/json'); -``` - -If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been -defined this way. - -But if you have been keeping attention you might argue that the following line should not work if the request object is -immutable. - -```php -$response->getBody()->write('Hello World!'); -``` - -The response-body implements a stream interface which is immutable for some reasons that are described in the -[meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be -aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in -the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. -I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content -to a response object. - -```php -$body = $response->getBody(); -$body->write('Hello World!'); -$response = $response->withBody($body); -``` - -Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our -output-logic a little bit more. Replace the line that echos the response-body with the following: - -```php -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); -``` - -This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a -webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. - -Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. - -Be sure to run composer phpstan, composer fix and composer check before moving on to the next chapter - - -[<< previous](04-development-helpers.md) | [next >>](06-router.md) diff --git a/implementation/18-caching/data/pages/06-router.md b/implementation/18-caching/data/pages/06-router.md deleted file mode 100644 index 6c39ae5..0000000 --- a/implementation/18-caching/data/pages/06-router.md +++ /dev/null @@ -1,101 +0,0 @@ -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) - -### Router - -A router dispatches to different handlers depending on rules that you have set up. - -With your current setup it does not matter what URL is used to access the application, it will always result in the same -response. So let's fix that now. - -I will use [nikic/fast-route](https://github.com/nikic/FastRoute) in this tutorial. But as always, you can pick your own -favorite package. - -Alternative packages: [symfony/Routing](https://github.com/symfony/Routing), [Aura.Router](https://github.com/auraphp/Aura.Router), [fuelphp/routing](https://github.com/fuelphp/routing), [Klein](https://github.com/chriso/klein.php) - -By now you know how to install Composer packages, so I will leave that to you. - -Now add this code block to your `Bootstrap.php` file where you added the 'hello world' message in the last chapter. - -```php -$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}); - -$routeInfo = $dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), -); - -switch ($routeInfo[0]) { - case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: - $response = (new \Laminas\Diactoros\Response)->withStatus(405); - $response->getBody()->write('Method not allowed'); - $response = $response->withStatus(405); - break; - case \FastRoute\Dispatcher::FOUND: - $handler = $routeInfo[1]; - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - /** @var \Psr\Http\Message\ResponseInterface $response */ - $response = call_user_func($handler, $request); - break; - case \FastRoute\Dispatcher::NOT_FOUND: - default: - $response = (new \Laminas\Diactoros\Response)->withStatus(404); - $response->getBody()->write('Not Found!'); - break; -} -``` - -In the first part of the code, you are registering the available routes for your application. In the second part, the -dispatcher gets called and the appropriate part of the switch statement will be executed. If a route was found, -we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. -If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. - -This setup might work for really small applications, but once you start adding a few routes your bootstrap file will -quickly get cluttered. So let's move them out into a separate file. - -Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; - -```php -addRoute('GET', '/hello[/{name}]', function (\Psr\Http\Message\ServerRequestInterface $request) { - $name = $request->getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - }); - $r->addRoute('GET', '/another-route', function (\Psr\Http\Message\ServerRequestInterface $request) { - $response = (new Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('This works too!'); - return $response; - }); -}; -``` - -Now let's rewrite the route dispatcher part to use the `Routes.php` file. - -```php -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback); -``` - -This is already an improvement, but now all the handler code is in the `routes.php` file. This is not optimal, so let's fix that in the next part. - -Of course we now need to add the 'config' folder to the configuration files of our -devhelpers so that they can scan that directory as well. - -[<< previous](05-http.md) | [next >>](07-dispatching-to-a-class.md) diff --git a/implementation/18-caching/data/pages/07-dispatching-to-a-class.md b/implementation/18-caching/data/pages/07-dispatching-to-a-class.md deleted file mode 100644 index 0c961a4..0000000 --- a/implementation/18-caching/data/pages/07-dispatching-to-a-class.md +++ /dev/null @@ -1,137 +0,0 @@ -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) - -### Dispatching to a Class - -In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html). -MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to -learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html) -and the followup posts. - -So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns). - -We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other -common names are 'Controllers' or 'Actions'. - -Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. -In there, create a `Hello.php` file. - -```php -getAttribute('name', 'Stranger'); - $response = (new \Laminas\Diactoros\Response)->withStatus(200); - $response->getBody()->write('Hello ' . $name . '!'); - return $response; - } -} -``` - -You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) -that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is -fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement -it in some other parts of our application as well. In order to use that Interface we have to require it with composer: -'composer require psr/http-server-handler'. - -The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. -At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. - -Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this: - -```php -return function(\FastRoute\RouteCollector $r) { - $r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class); - $r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class); -}; -``` - -Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared -the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing -the response to the one we defined for the second route. - -To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`: - -```php -case \FastRoute\Dispatcher::FOUND: - $handler = new $routeInfo[1]; - if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) { - throw new \Exception('Invalid Requesthandler'); - } - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - assert($response instanceof \Psr\Http\Message\ResponseInterface) - break; -``` - -So instead of just calling a method you are now instantiating an object and then calling the method on it. - -Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. - -And of course don't forget to commit your changes. - -Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still -generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for -example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still -have our whoopsie error-handler but i like to be more explicit in my control flow. - -In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new -Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and -InternalServerError. All three should extend phps Base Exception class. - -Here is my NotFound.php for example. - -```php - $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - break; - case Dispatcher::METHOD_NOT_ALLOWED: - throw new MethodNotAllowed; - - case Dispatcher::NOT_FOUND: - default: - throw new NotFound; - } -} catch (MethodNotAllowed) { - $response = (new Response)->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = (new Response)->withStatus(404); - $response->getBody()->write('Not Found'); -} catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); -} -``` - -Check if our code still works, try to trigger some errors, run phpstan and the fix command -and don't forget to commit your changes. - -[<< previous](06-router.md) | [next >>](08-inversion-of-control.md) diff --git a/implementation/18-caching/data/pages/08-inversion-of-control.md b/implementation/18-caching/data/pages/08-inversion-of-control.md deleted file mode 100644 index 21f4f23..0000000 --- a/implementation/18-caching/data/pages/08-inversion-of-control.md +++ /dev/null @@ -1,54 +0,0 @@ -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) - -### Inversion of Control - -In the last part you have set up a controller class and generated our Http-Response-object in that class, but if we -want to switch to a more powerfull Http-Implementation later, or need to create our own for some special purposes, then -we would need to edit every one of our request handlers to call a different constructor of the class. - -The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that -instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done -with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). - -If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is -implemented, it will make sense. - -Change your `Hello` action to the following: - -```php -getAttribute('name', 'Stranger'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -Now the code will result in an error because we are not actually injecting anything. So let's fix that in the `Bootstrap.php` where we dispatch when a route was found: - -```php -$handler = new $className($response); -``` - -Of course we need to also update all the other handlers. - -[<< previous](07-dispatching-to-a-class.md) | [next >>](09-dependency-injector.md) diff --git a/implementation/18-caching/data/pages/09-dependency-injector.md b/implementation/18-caching/data/pages/09-dependency-injector.md deleted file mode 100644 index 7f7c6a2..0000000 --- a/implementation/18-caching/data/pages/09-dependency-injector.md +++ /dev/null @@ -1,213 +0,0 @@ -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) - -### Dependency Injector - -A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when -the class is instantiated. - -Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work -with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look -for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). - -I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) -out of the box. - -After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: - -```php -addDefinitions([ - \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), - \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), -]); - -return $builder->build(); -``` - -In this file we create a containerbuilder, add some definitions to it and return the container. -As the container supports autowiring we only need to define services where we want to use a specific implementation of -an interface. - -In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to -tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second -exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. - -Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), -as we will use that extensively. - -Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally -to create our Handler-Object - -```php -$container = require __DIR__ . '/../config/dependencies.php'; -assert($container instanceof \Psr\Container\ContainerInterface); - -$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); -assert($request instanceof \Psr\Http\Message\ServerRequestInterface); -``` - -The other part that has to be changed is the dispatching of the route. Before you had the following code: - -```php -$className = $routeInfo[1]; -$handler = new $className($response); -assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Change that to the following: - -```php -/** @var RequestHandlerInterface $handler */ -$className = $routeInfo[1]; -$handler = $container->get($className); -assert($handler instanceof RequestHandlerInterface); -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Make sure to use the container fetch the response object in the catch blocks as well: - -```php -} catch (MethodNotAllowed) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(404); - $response->getBody()->write('Not Found'); -} -``` - -Now all your controller constructor dependencies will be automatically resolved with PHP-DI. - -We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons -in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call -`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we -want to work with a different date in a test. - -Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation -that creates us a DateTimeImmutable object with the current date and time. - -src/Service/Time/Now.php: -```php -namespace Lubian\NoFramework\Service\Time; - -interface Now -{ - public function __invoke(): \DateTimeImmutable; -} -``` -src/Service/Time/SystemClockNow.php: -```php -namespace Lubian\NoFramework\Service\Time; - -final class SystemClockNow implements Now -{ - - public function __invoke(): \DateTimeImmutable - { - return new \DateTimeImmutable; - } -} -``` -If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and -update the handle-method to use the new class property: - -```php -getAttribute('name', 'Stranger'); - $nowAsString = ($this->now)()->format('H:i:s'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowAsString); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI -automatically figures out what classes are requested in the constructor and tries to create the objects needed. - -But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred -S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: - -```php - public function __construct( - private ResponseInterface $response, - private Now $now, - ) -``` - -When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation -should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: - -```php -\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), -``` - -we could also use the PHP-DI create method to delegate the object creation to the container implementation: -```php -\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), -``` - -this way the container can try to resolve any dependencies that the class might have internally, but prefer the other -method because we are not depending on this specific dependency injection implementation. - -Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are -requesting the Hello action. - -If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As -we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way -we wont get any warnings for this particular issue, but for any other issues we add to our code. - -Update the phpstan.neon file to include a "baseline" file: - -``` -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` - -if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and -ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just -'baseline' - -[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/implementation/18-caching/data/pages/10-invoker.md b/implementation/18-caching/data/pages/10-invoker.md deleted file mode 100644 index 3033fae..0000000 --- a/implementation/18-caching/data/pages/10-invoker.md +++ /dev/null @@ -1,102 +0,0 @@ -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) - -### Invoker - -Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the -one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long -with some services and not the whole Requestobject with all its various properties. - -If we take our Hello action for example we only need a response object, the time service and the 'name' information from -the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named -the class hello and it would be redundant to also call the the method hello. So an updated version of that class could -look like this: - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - string $name = 'Stranger', - ): ResponseInterface - { - $body = $this->response->getBody(); - $nowString = $now->get()->format('H:i:s'); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowString); - return $response - ->withBody($body) - ->withStatus(200); - } -} -``` - -It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short -closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the -rootpath of our application yet. - -```php -$r->addRoute('GET', '/hello[/{name}]', Hello::class); -$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); -$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); -``` - -In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the -route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that -class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what -arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router -if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple -callable we would need to manually use reflection as well to resolve all the arguments. - -But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call) -which handles all of that for us. - -After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo -switch section in the Bootstrap.php file to use the container->call() method: - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2]; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$response = $container->call($handler, $args); -``` - -Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. - -But by now you should know that I do not like to depend on specific implementations and the call method is not defined in -the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container -or any other implementation. - -Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific -container implementation so we could use it with any container that implements the ContainerInterface. And best of all -the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that -we could implement if we ever want to write our own implementation or we could write an adapter that uses a different -class that solves the same problem. - -But for now we are using the solution provided by PHP-DI. -So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block - -```php -$handler = $routeInfo[1]; -$args = $routeInfo[2] ?? []; -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$args['request'] = $request; -$invoker = $container->get(InvokerInterface::class); -assert($invoker instanceof InvokerInterface); -$response = $invoker->call($handler, $args); -assert($response instanceof ResponseInterface); -``` - -Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php) -by php, and even some more. - -But let us move on to something more fun and add some templating functionality to our application as we are trying to build -a website in the end. - -[<< previous](09-dependency-injector.md) | [next >>](11-templating.md) diff --git a/implementation/18-caching/data/pages/11-templating.md b/implementation/18-caching/data/pages/11-templating.md deleted file mode 100644 index 7bfe1aa..0000000 --- a/implementation/18-caching/data/pages/11-templating.md +++ /dev/null @@ -1,236 +0,0 @@ -[<< previous](10-invoker.md) | [next >>](12-configuration.md) - -### Templating - -A template engine is not necessary with PHP because the language itself can take care of that. But it can make things -like escaping values easier. They also make it easier to draw a clear line between your application logic and the -template files which should only put your variables into the HTML code. - -A good quick read on this is [ircmaxell on templating](http://blog.ircmaxell.com/2012/12/on-templating.html). Please -also read [this](http://chadminick.com/articles/simple-php-template-engine.html) for a different opinion on the topic. -Personally I don't have a strong opinion on the topic, so decide yourself which approach works better for you. - -For this tutorial we will use a PHP implementation of [Mustache](https://github.com/bobthecow/mustache.php). So install -that package before you continue (`composer require mustache/mustache`). - -Another well known alternative would be [Twig](http://twig.sensiolabs.org/). - -Now please go and have a look at the source code of the -[engine class](https://github.com/bobthecow/mustache.php/blob/master/src/Mustache/Engine.php). As you can see, the class -does not implement an interface. - -You could just type hint against the concrete class. But the problem with this approach is that you create tight -coupling. - -In other words, all your code that uses the engine will be coupled to this mustache package. If you want to change the -implementation you have a problem. Maybe you want to switch to Twig, maybe you want to write your own class or you want -to add functionality to the engine. You can't do that without going back and changing all your code that is tightly -coupled. - -What we want is loose coupling. We will type hint against an interface and not a class/implementation. So if you need -another implementation, you just implement that interface in your new class and inject the new class instead. - -Instead of editing the code of the package we will use the [adapter pattern](http://en.wikipedia.org/wiki/Adapter_pattern). -This sounds a lot more complicated than it is, so just follow along. - -First let's define the interface that we want. Remember the [interface segregation principle](http://en.wikipedia.org/wiki/Interface_segregation_principle). -This means that instead of large interfaces with a lot of methods we want to make each interface as small as possible. -A class can implement multiple interfaces if necessary. - -So what does our template engine actually need to do? For now we really just need a simple `render` method. Create a -new folder in your `src/` folder with the name `Template` where you can put all the template related things. - -In there create a new interface `Renderer.php` that looks like this: - -```php - $data */ - public function render(string $template, array $data = []) : string; -} -``` - -Now that this is sorted out, let's create the implementation for mustache. In the same folder, create the file -`MustacheRenderer.php` with the following content: - -```php -engine->render($template, $data); - } -} -``` - -As you can see the adapter is really simple. While the original class had a lot of methods, our adapter is really simple -and only fulfills the interface. - -Of course we also have to add a definition in our `dependencies.php` file because otherwise the container won't know -which implementation he has to inject when you hint for the interface. Add this line: - -```php -[ - ... - \Lubian\NoFramework\Template\Renderer::class => DI\create(\Lubian\NoFramework\Template\MustacheRenderer::class) - ->constructor(new Mustache_Engine), -] -``` - -Now update the Hello.php class to require an implementation of our renderer interface -and use that to render a string using mustache syntax. - - -```php -final class Hello -{ - public function __invoke( - ResponseInterface $response, - Now $now, - Renderer $renderer, - string $name = 'Stranger', - ): ResponseInterface { - $body = $response->getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render( - 'Hello {{name}}, the time is {{now}}!', - $data, - ); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} -``` - -Now go check quickly in your browser if everything works. By default Mustache uses a simple string handler. -But what we want is template files, so let's go back and change that. - -To make this change we need to pass an options array to the `Mustache_Engine` constructor. So let's go back to the -`dependencies.php` file and add the following code: - -```php -[ - ... - Mustache_Loader_FilesystemLoader::class => fn() => new Mustache_Loader_FilesystemLoader(__DIR__ . '/../templates', ['extension' => '.html']), - Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $MLFsl) => new Mustache_Engine(['loader' => $MLFsl]), -] -``` - -We are passing an options array because we want to use the `.html` extension instead of the default `.mustache` extension. -Why? Other template languages use a similar syntax and if we ever decide to change to something else then we won't have -to rename all the template files. - -To let PHP-DI use its magic for creating our MustacheRenderer class we need to tell it exactly how to wire all the -dependencies, therefore I defined how to create the Filesystemloader, on the next line we typehinted that loader -in the short closure which acts as a factory method for the Mustache_Engine, as PHP-DI automatically injects the Object -we can then use it in the factory. - -In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: - -``` -

Hello World

-Hello {{ name }} -``` - -Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` - -Navigate to the hello page in your browser to make sure everything works. - -One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies -file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration -values. - -Lets create a 'Settings' class in our './src' Folder: - -```php -addDefinitions([ - Settings::class => fn () => require __DIR__ '/settings.php', - ResponseInterface::class => create(Response::class), - ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), - Renderer::class => fn (ME $me) => new Mustache($me), - MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), -]); - -return $builder->build(); -``` - - - -And as always, don't forget to commit your changes. - - -[<< previous](10-invoker.md) | [next >>](12-configuration.md) diff --git a/implementation/18-caching/data/pages/12-configuration.md b/implementation/18-caching/data/pages/12-configuration.md deleted file mode 100644 index 4b60c19..0000000 --- a/implementation/18-caching/data/pages/12-configuration.md +++ /dev/null @@ -1,200 +0,0 @@ -[<< previous](11-templating.md) | [next >>](13-refactoring.md) - -### Configuration - -In the last chapter we added some more definitions to our dependencies.php in that definitions -we needed to pass quite a few configuration settings and filesystem strings to the constructors -of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. - -As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. - -As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. - -Lets create a 'Settings' class in our './src' Folder: - -```php -filePath; - } -} -``` - -If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files -and craft a settings object from them. - -As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another -factory for our Container because everyone should have a Friend :) - -```php -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn () => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} -``` - -For this to work we need to change our dependencies.php file to just return the array of definitions: -And here we can instantly use the Settings object to create our template engine. - -```php - fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), -]; -``` - -Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: - -```php -... -error_reporting(E_ALL); - -$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); -$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); -... -``` - -Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. - -[<< previous](11-templating.md) | [next >>](13-refactoring.md) diff --git a/implementation/18-caching/data/pages/13-refactoring.md b/implementation/18-caching/data/pages/13-refactoring.md deleted file mode 100644 index 6dbbb8d..0000000 --- a/implementation/18-caching/data/pages/13-refactoring.md +++ /dev/null @@ -1,373 +0,0 @@ -[<< previous](12-configuration.md) | [next >>](14-middleware.md) - -### Refactoring - -By now our Bootstrap.php file has grown quite a bit, and with the addition of our dependency container there is now no -reason not to introduce a lot of classes and interfaces for all the that are happening in the bootstrap file. -After all the bootstrap file should just set up the classes needed for the handling logic and execute them. - -At the bottom of our Bootstrap.php we have our Response-Emitter Logic, lets create an Interface and a class for that. -As I am really lazy I just selected the code in PhpStorm, klicken on 'Refactor -> extract method' then selected the -method and clicked on 'Refactor -> extract class'. I choose 'BasicEmitter' for the classname, changed the method to non -static and extracted an interface. - -'./src/Http/Emitter.php' -```php -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - echo $response->getBody(); - } -} -``` -After registering the BasicEmitter to implement the Emitter interface in the dependencies file you can use the following -code in the Bootstrap.php to emit the response: - -```php -/** @var Emitter $emitter */ -$emitter = $container->get(Emitter::class); -$emitter->emit($response); -``` - -If at some point you need a [more advanced emitter](https://github.com/httpsoft/http-emitter), you could now easily -write an adapter that implements your emitter interface and wraps that more advanced emitter - -Now that we have our Emitter in a seperate class we need to take care of the big block that handles our routing and -calling the routerhandler that in the passes the request to a function and gets the response. - -For this to steps to be seperated we are going to create two more classes: -1. a RouteDecorator, that finds the correct handler for the requests and adds its findings to the Request Object -2. A Requesthandler that implements the RequestHandlerInterface, gets the information for the request handler from the - requestobject, fetches the correct object from the container and calls it to create a response. - -Lets create the HandlerInterface first: - -```php -getAttribute($this->routeAttributeName, false); - assert($handler !== false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} - -``` - -We will define our routing class to implement the MiddlewareInterface, you can install that with 'composer require psr/http-server-middleware'. -The interface requires us to implement a method called 'process' a Request as its first argument and an RequestHandler -as the second one. The return value of the method needs to be a Responseobject. We will learn more about Middlewares in -the next chapter. - -```php -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} -``` - -Before we can use all the new services in our Bootstrap file we need to add the definitions to our container. -```php -[ - '...', - Emitter::class => fn (BasicEmitter $e) => $e, - RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h, - MiddlewareInterface::class => fn (RouteMiddleware $r) => $r, - Dispatcher::class => fn (Settings $s) => simpleDispatcher(require __DIR__ . '/routes.php'), - ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf, -], -``` - -And then we can update our Bootstrap.php to fetch all the services and let them handle the request. - -```php -... -$routeMiddleWare = $container->get(MiddlewareInterface::class); -assert($routeMiddleWare instanceof MiddlewareInterface); -$handler = $container->get(RoutedRequestHandler::class); -assert($handler instanceof RequestHandlerInterface); -$emitter = $container->get(Emitter::class); -assert($emitter instanceof Emitter); - -$request = $container->get(ServerRequestInterface::class); -assert($request instanceof ServerRequestInterface); - -$response = $routeMiddleWare->process($request, $handler); -$emitter->emit($response); -``` -Now we have wrapped all the important parts in our Bootstrap.php into seperate classes, but it is still quite a lot of -code and also many calls the container (and i have to write way too many docblocks to that phpstan doenst yell at me). - -So we should just add another class that wraps all of our Request-Handling Classes into a clearly defined structure. - -I will follow symfonys example and call this class our kernel. Before i create that class i will recap what our class -should require to function properly. - -* A RequestFactory - We want our Kernel to be able to build the request itself -* An Emitter - Without an Emitter we will not be able to send the response to the client -* RouteMiddleware - To decore the request with the correct handler for the requested route -* RequestHandler - To delegate the request to the correct funtion that creates the response - -As the Psr ContainerInterface leaves us to much handiwork to easily create a Serverrequest I will extend that interface -to give us easier access to a requestobject and wrap the Diactorors RequestFactory in an Adapter that satisfies our -interface: - -```php -factory::fromGlobals(); - } - - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} -``` - -For later shenanigans I will let our Kernel implement the RequestHandlerInterface, this is how my version looks now: - -```php -routeMiddleware->process($request, $this->handler); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} - -``` - -We can now replace everything after the ErrorHandler in our Bootstrap.php with these few lines - -```php -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); -``` - -You might get some Errors here because the Container cannot resolve all the dependencies, try to fix those errors by looking -at the Whoops output and adding the needed definitions to the dependencies.php file. - -And as always, don't forget to commit your changes. - -[<< previous](12-configuration.md) | [next >>](14-middleware.md) diff --git a/implementation/18-caching/data/pages/14-middleware.md b/implementation/18-caching/data/pages/14-middleware.md deleted file mode 100644 index 81f82a5..0000000 --- a/implementation/18-caching/data/pages/14-middleware.md +++ /dev/null @@ -1,303 +0,0 @@ -[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) - -### Middleware - -In the last chapter we wrote our RouterClass to implement the middleware interface, and in this chapter I want to explain -a bit more about what this interface does and why it is used in many applications. - -The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets -passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all -the middlewars to the client/emitter. - -So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the -response after it gets created by our handlers. - -So lets take a look at the middleware and the requesthandler interfaces - -```php -interface MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; -} - -interface RequestHandlerInterface -{ - public function handle(ServerRequestInterface $request): ResponseInterface; -} -``` - -The RequestHandlerInterface gets only a request and returns a response, the MiddlewareInterface gets a request and a -requesthandler and returns a response. So the logical thing for the Middleware is to use the handler to produce the -response. - -But the middleware could just ignore the handler and produce a response on its own as the interface just requires us -to produce a response. - -A simple example for that would be a caching middleware. The basic idea is that we want to cache all request from users -that are not logged in. This way we can save a lot of processing power in rendering the html and fetching data from the -database. - -In this scenario we assume that we have an authentication middleware that checks if a user is logged in and decorates -the request with an 'isAuthenticated' attribute. - -If the 'isAuthenticated' attribute is set to false, we check if we have a cached response and return that, if that -response is not already cached, than we let the handler create the response and store that in the cache for a few -seconds - -```php -interface CacheInterface -{ - public function get(string $key, callable $resolver, int $ttl): mixed; -} -``` - -The first parameter is the identifier for the cache, the second is a callable that produces the value and the last one -defines the seconds that the cache should keep the item. If the cache doesnt have an item with the given key then it uses -the callable to produce the value and stores it for the time specified in ttl. - -so lets write our caching middleware: - -```php -final class CachingMiddleware implements MiddlewareInterface -{ - public function __construct(private CacheInterface $cache){} - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - if ($request->getAttribute('isAuthenticated', false) && $request->getMethod() === 'GET') { - $key = $request->getUri()->getPath(); - return $this->cache->get($key, fn() => $handler->handle($request), 10); - } - return $handler->handle($request); - } -} -``` - -we can also modify the response after it has been created by our application, for example we could implement a gzip -middleware, or for more simple and silly example a middleware that adds a Dank Meme header to all our response so that the browser -know that our application is used to serve dank memes: - -```php -final class DankMemeMiddleware implements MiddlewareInterface -{ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - $response = $handler->handle($request); - return $response->withAddedHeader('Meme', 'Dank'); - } -} -``` - -but for our application we are going to just add two external middlewares: - -* [Trailing-slash](https://github.com/middlewares/trailing-slash) to remove the trailing slash from all routes. -* [whoops middleware](https://github.com/middlewares/whoops) to wrap our error handler into a nice middleware - -```bash -composer require middlewares/trailing-slash -composer require middlewares/whoops -``` - -The whoops middleware should be the first middleware to be executed so that we catch any errors that are thrown in the -application as well as the middleware stack. - -Our desired request -> response flow looks something like this: - - Client - | ^ - v | - Kernel - | ^ - v | - Whoops Middleware - | ^ - v | - TrailingSlash - | ^ - v | - Routing - | ^ - v | - ContainerResolver - | ^ - v | - Controller/Action - -As every middleware expects a RequestHandlerInterface as its second argument we need some extra code that wraps every -middleware as a RequestHandler and chains them together with the ContainerRouteDecoratedResolver as the last Handler. - -```php -interface Pipeline -{ - public function dispatch(ServerRequestInterface $request): ResponseInterface; -} -``` - -And our implementation looks something like this: - -```php - $middlewares - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} -``` - -Here we define our constructor to require two arguments: an array of middlewares and a requesthandler as the final code -that should produce our response. - -In the buildStack() method we wrap every middleware as a RequestHandler with the current tip property as the $next argument -and store that itself as the current tip. - -There are of course a lot of more sophisticated ways to build a pipeline/dispatcher that you can check out at the [middlewares github](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) - -Lets add a simple factory to our dependencies.php file that creates our middlewarepipeline -Lets create a simple Factory that loads an Array of Middlewares from the Config folder and uses that to build our pipeline - -```php -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} -``` - -And configure the container to use the Factory to create the Pipeline: - -```php - ..., - Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(), - ... -``` -And of course a new file called middlewares.php in our config folder: -```php -pipeline->dispatch($request); -} -``` - -Lets try if you can make the kernel work with our created Pipeline implementation. For the future we could improve our -pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a -dependency container, if you want you can do that as well. - -**A quick note about docblocks:** You might have noticed, that I rarely add docblocks to my the code in the examples, and -when I do it seems kind of random. My philosophy is that I only add docblocks when there is no way to automatically get -the exact type from the code itself. For me docblocks only serve two purposes: help my IDE to understand what it choices -it has for code completion and to help the static analysis to better understand the code. There is a great blogpost -about the [cost and value of DocBlocks](https://localheinz.com/blog/2018/05/06/cost-and-value-of-docblocks/), although it -is written in 2018 at a time before PHP 7.4 was around everything written there is still valid today. - -[<< previous](12-refactoring.md) | [next >>](15-adding-content.md) diff --git a/implementation/18-caching/data/pages/15-adding-content.md b/implementation/18-caching/data/pages/15-adding-content.md deleted file mode 100644 index 64562fa..0000000 --- a/implementation/18-caching/data/pages/15-adding-content.md +++ /dev/null @@ -1,253 +0,0 @@ -[<< previous](14-middleware.md) | [next >>](16-data-repository.md) - -### Adding Content - -By now we did not really display anything but some examples to in our application and it is now time to make our app -display some content. For example we could our app be able to display the Markdown files used in this tutorial as -nicely rendered HTML Pages that can be viewed in the browser instead of the editor you are using. - -So lets start by copying the markdown files to our app directory. I have created a new folder 'data/pages' and placed all -the markdown files in there. - -Next we need a markdown parser, a pretty simple one is [Parsedown](https://parsedown.org/), if you want more features -you could also use the [Commonmark parser](https://commonmark.thephpleague.com/). I will choose Parsedown here, but you -can use whatever you like. - -After installing Parsedown lets write a Markdownparser interface and an implementation using parsedown. - -We only need one function that receives a string of Markdown and returns the HTML represantion (as a string as well). - - - -```php -parser->parse($markdown); - } -} -``` - -We could now use the ParsedownRender class directly in our actions by typehinting the classname as an argument to the -constructor or a method, but as we always want to rely on an interface instead of an implementation we need to define -the the ParsedownRenderer as the correct implementation for the MarkdownRenderer interface in the dependencies file: - -```php -... - \Lubian\NoFramework\Template\MarkdownParser::class => fn(\Lubian\NoFramework\Template\ParsedownParser $p) => $p, -... -``` - -You can test that in our "Other.php" action and try out if the Parser works and is able to render Markdown to HTML: - -```php -public function someFunctionName(ResponseInterface $response, MarkdownParser $parser): ResponseInterface -{ - $html = $parser->parse('This *works* **too!**'); - $response->getBody()->write($html); - return $response->withStatus(200); -} -``` - -But we want to display complete Pages written in Markdown, it would also be neat to be able to display a list of all -available pages. For that we need a few things: - -Firstly we need two new Templates, one for the list of the Pages, and the second one for displaying a single pages -content. Create a new folder in `templates/page` with to files: - -`templates/page/list.html` -```html - - - - - Pages - - - -
- -
- - -``` - -This template iterates over a provided array of pages, each element consists of the two properties: an id and a title, -those are simply displayed using an unordered list. - -`templates/page/show.html` -```html - - - - - {{title}} - - - - - - -
- {{{content}}} -
- - -``` - -The second templates displays a single rendered markdown page. As data it expects the title and the content as array. -I used an extra bracket for the content ```{{{content}}}``` so that the Mustache-Renderer does not escape the provided -html and thereby destroys the the parsed markdown. - -You might have spotted that I added [Pico.css](https://picocss.com/) which is just a very small css framework to make the -pages a little bit nicer to look at. It mostly provides some typography styles that work great with rendered Markdown, -but you can leave that out or use any other css framework you like. There is also some Javascript that adds syntax -highlighting to the code. - -After you have taken care of the templating side we can now create an new Action class with two methods to display use -our markdown files and the templates to create the pages. As we have two templates I propose to use Two methods in our -Action: -`src/Action/Page.php` -```php -function show(string $name): \Psr\Http\Message\ResponseInterface; -function list(): \Psr\Http\Message\ResponseInterface; -``` - -Lets define two routes. `/page` should display the overview of all pages, and if the add the name of chapter to the -route, `/page/adding-content` for example, the show action should be called with the name as a variable: - -`config/routes.php` -```php -$r->addRoute('GET', '/page', [Page::class, 'list']); -$r->addRoute('GET', '/page/{page}', [Page::class, 'show']); -``` - -Here is my Implementation. If have added a little regex replacement in the show method that replaces the links to the -next and previous chapter so that it works with our routing configuration. - -`src/Action/Page.php` -```php -getPages(), - fn (string $filename) => str_contains($filename, $page) - ) - )[0]; - $markdown = file_get_contents($page); - - // fix the next and previous buttons to work with our routing - $markdown = preg_replace('/\(\d\d-/m', '(', $markdown); - $markdown = str_replace('.md)', ')', $markdown); - - $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); - $data = [ - 'title' => substr($page, 3), - 'content' => $this->parser->parse($markdown), - ]; - $html = $this->renderer->render('page/show', $data); - $this->response->getBody()->write($html); - return $this->response; - } - - public function list(): ResponseInterface - { - $pages = array_map(function (string $page) { - $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page); - return [ - 'id' => substr($page, 0, 2), - 'title' => substr($page, 3), - ]; - }, $this->getPages()); - $html = $this->renderer->render('page/list', ['pages' => $pages]); - $this->response->getBody()->write($html); - return $this->response; - } - - /** - * @return string[] - */ - private function getPages(): array - { - $files = glob($this->pagesPath . '*.md'); - if ($files === false) { - throw new InternalServerError('cannot read pages'); - } - return $files; - } -} -``` - -You can now navigate your Browser to [localhost:1234/page][http://localhost:1234/page] and try out if everything works. - -Of course this code is far from looking good. We heavily rely on the pages being files in the filesystem, and the action -should never be aware of the filesystem in the first place, also we have a lot of string replacements and other repetetive -code in the file. And phpstan is gonna scream at us a lot, but if we rewrite the code to satisfy all the checks we would -add even more lines to that simple class, so lets move on to the next chapter where we move all the logic to seperate -classes following our holy SOLID principles :) - - -[<< previous](14-middleware.md) | [next >>](16-data-repository.md) diff --git a/implementation/18-caching/data/pages/16-data-repository.md b/implementation/18-caching/data/pages/16-data-repository.md deleted file mode 100644 index d9a3218..0000000 --- a/implementation/18-caching/data/pages/16-data-repository.md +++ /dev/null @@ -1,265 +0,0 @@ -[<< previous](15-adding-content.md) | [next >>](17-performance.md) - -## Data Repository - -At the end of the last chapter I mentioned being unhappy with our Pages action, because there is to much stuff happening -there. We are firstly receiving some Arguments, then we are using those to query the filesytem for the given page, -loading the specific file from the filesystem, rendering the markdown, passing the markdown to the template renderer, -adding the resulting html to the response and then returning the response. - -In order to make our pageaction independent from the filesystem and move the code that is responsible for reading the -files -to a better place I want to introduce -the [Repository Pattern](https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html). - -I want to start by creating a class that represents the Data that is included in a page so that. For now I can spot -three -distrinct attributes. - -* the ID (or chapternumber) -* the title (or name) -* the content - -Currently all those properties are always available, but we might later be able to create new pages and store them, but -at that point in time we are not yet aware of the new available ID, so we should leave that property nullable. This -allows -us to create an object without an id and let the code that actually saves the object to a persistant store define a -valid -id on saving. - -Lets create an new Namespace called `Model` and put a `MarkdownPage.php` class in there: - -```php -dataPath . '*.md'); - if ($files === false) { - throw new InternalServerError('cannot read pages'); - } - return array_map(function (string $filename) { - $content = file_get_contents($filename); - if ($content === false) { - throw new InternalServerError('cannot read pages'); - } - $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); - return new MarkdownPage( - (int) substr($idAndTitle, 0, 2), - substr($idAndTitle, 3), - $content - ); - }, $files); - } - - public function byName(string $name): MarkdownPage - { - $pages = array_values( - array_filter( - $this->all(), - fn (MarkdownPage $p) => $p->title === $name, - ) - ); - - if (count($pages) !== 1) { - throw new NotFound; - } - - return $pages[0]; - } -} -``` - -With that in place we need to add the required `$pagesPath` to our settings class and add specify that in our -configuration. - -`src/Settings.php` - -```php -final class Settings -{ - public function __construct( - public readonly string $environment, - public readonly string $dependenciesFile, - public readonly string $middlewaresFile, - public readonly string $templateDir, - public readonly string $templateExtension, - public readonly string $pagesPath, - ) { - } -} -``` - -`config/settings.php` - -```php -return new Settings( - environment: 'prod', - dependenciesFile: __DIR__ . '/dependencies.php', - middlewaresFile: __DIR__ . '/middlewares.php', - templateDir: __DIR__ . '/../templates', - templateExtension: '.html', - pagesPath: __DIR__ . '/../data/pages/', -); -``` - -Of course we need to define the correct implementation for the container to choose when we are requesting the Repository -interface: -`conf/dependencies.php` - -```php -MarkdownPageRepo::class => fn (FileSystemMarkdownPageRepo $r) => $r, -FileSystemMarkdownPageRepo::class => fn (Settings $s) => new FileSystemMarkdownPageRepo($s->pagesPath), -``` - -Now you can request the MarkdownPageRepo Interface in your page action and use the defined functions to get the -MarkdownPage -Objects. My `src/Action/Page.php` looks like this now: - -```php -repo->byName($page); - - // fix the next and previous buttons to work with our routing - $content = preg_replace('/\(\d\d-/m', '(', $page->content); - assert(is_string($content)); - $content = str_replace('.md)', ')', $content); - - $data = [ - 'title' => $page->title, - 'content' => $this->parser->parse($content), - ]; - - $html = $this->renderer->render('page/show', $data); - $this->response->getBody()->write($html); - return $this->response; - } - - public function list(): ResponseInterface - { - $pages = array_map(function (MarkdownPage $page) { - return [ - 'id' => $page->id, - 'title' => $page->content, - ]; - }, $this->repo->all()); - - $html = $this->renderer->render('page/list', ['pages' => $pages]); - $this->response->getBody()->write($html); - return $this->response; - } -} -``` - -Check the page in your browser if everything still works, don't forget to run phpstan and the others fixers before -committing your changes and moving on to the next chapter. - -[<< previous](15-adding-content.md) | [next >>](17-performance.md) diff --git a/implementation/18-caching/data/pages/17-performance.md b/implementation/18-caching/data/pages/17-performance.md deleted file mode 100644 index c83c7d5..0000000 --- a/implementation/18-caching/data/pages/17-performance.md +++ /dev/null @@ -1,43 +0,0 @@ -[<< previous](16-data-repository.md) | [next >>](18-caching.md) - -## Autoloading performance - -Although our application is still very small and you should not really experience any performance issues right now, -there are still some things we can already consider and take a look at. If I check the network tab in my browser it takes -about 90-400ms to show a simple rendered markdownpage, with is sort of ok but in my opinion way to long as we are not -really doing anything and do not connect to any external services. Mostly we are just reading around 16 markdown files, -a template, some config files here and there and parse some markdown. So that should not really take that long. - -The problem is, that we heavily rely on autoloading for all our class files, in the `src` folder. And there are also -quite a lot of other files in composers `vendor` directory. To understand while this is becomming we should make -ourselves familiar with how [autoloading in php](https://www.php.net/manual/en/language.oop5.autoload.php) works. - -The basic idea is, that every class that php encounters has to be loaded from somewhere in the filesystem, we could -just require the files manually but that is tedious, unflexible and can often cause errors. - -The problem we are now facing is that the composer autoloader has some rules to determine from where in the filesystem -a class definition might be placed, then the autoloader tries to locate a file by the namespace and classname and if it -exists includes that file. - -If we only have a handfull of classes that does not take a lot of time, but as we are growing with our application this -easily takes longer than necesery, but fortunately composer has some options to speed up the class loading. - -Take a few minutes to read the documentation about [composer autoloader optimization](https://getcomposer.org/doc/articles/autoloader-optimization.md) - -You can try all 3 levels of optimizations, but we are going to stick with the first one for now, so lets create an -optimized classmap. - -`composer dump-autoload -o` - -After composer has finished you can start the devserver again with `composer serve` and take a look at the network tab -in your browsers devtools. - -In my case the response time falls down to under an average of 30ms with some spikes in between, but all in all it looks really good. -You can also try out the different optimization levels and see if you can spot any differences. - -Although the composer manual states not to use the optimtization in a dev environment I personally have not encountered -any errors with the first level of optimizations, so we can use that level here. If you add the line from the documentation -to your `composer.json` so that the autoloader gets optimized everytime we install new packages. - - -[<< previous](16-data-repository.md) | [next >>](18-caching.md) diff --git a/implementation/18-caching/data/pages/18-caching.md b/implementation/18-caching/data/pages/18-caching.md deleted file mode 100644 index 42e9cb1..0000000 --- a/implementation/18-caching/data/pages/18-caching.md +++ /dev/null @@ -1,252 +0,0 @@ -[<< previous](17-performance.md) | [next >>](19-database.md) - -**DISClAIMER** I do not really have a lot of experience when it comes to caching, so this chapter is mostly some random -thoughts and ideas I wanted to explore when writing this tutorial, you should definitely take everything that is being -said here with caution and try to read up on some other sources. But that holds true for the whole tutorial anyway :) - -## Caching - -In the last chapter we greatly improved the perfomance for the lookup of all our classfiles, but currently we do not -have any real bottlenecks in our application like complex queries. - -But in a real application we are going to execute some really heavy and time intensive database queries that can take -quite a while to be completed. - -We can simulate that by adding a simple delay in our `FileSystemMarkdownPageRepo`. - -```php - return array_map(function (string $filename) { - usleep(rand(100, 400) * 1000); - $content = file_get_contents($filename); - if ($content === false) { - throw new InternalServerError('cannot read pages'); - } - $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); - return new MarkdownPage( - (int) substr($idAndTitle, 0, 2), - substr($idAndTitle, 3), - $content - ); -}); -``` - -Here I added a function that pauses the scripts execution for a random time between 100 and 400ms for every markdownpage -in every call of the `all()` method. - -If you open any page or even the listAction in you browser you will see, that it takes quite a time to render that page. -Although this is a silly example we do not really need to query the database on every request, so lets add a way to cache -the database results between requests. - -The PHP-Community has already adressed the issue of having easy to use access to cache libraries, there is the -[PSR-6 Caching Interface](https://www.php-fig.org/psr/psr-6) which gives us easy access to many different implementations, -then there is also a much simpler [PSR-16 Simple Cache](https://www.php-fig.org/psr/psr-16) which makes the use even more -easy, and most Caching Libraries implement Both interfaces anyway. You would think that this is more than enough solutions -to satisfy all the Caching needs around, but the Symfony People decided that Caching should be even simpler and easier -to use and defined their own [Interface](https://symfony.com/doc/current/components/cache.html#cache-component-contracts) -which only needs two methods. You should definitely take a look at the linked documentation as it really blew my mind -when I first encountered it. - -The basic idea is that you provide a callback that computes the requested value. The Cache implementation then checks -if it already has the value stored somewhere and if it doesnt it just executes the callback and stores the value for -future calls. - -It is really simple and great to use. In a real world application you should definitely use that or a PSR-16 implementation -but for this tutorial I wanted to roll out my own solution, so here we go. - -As always we are going to define an interface first, I am going to call it EasyCache and place it in the `Service/Cache` -namespace. I will require only one method which is base on the Symfony Cache Contract, and hast a key, a callback, and -the duration that the item should be cached as arguments. - -```php -cache->get( - $key, - fn () => $this->repo->all(), - 300 - ); - } - - public function byName(string $name): MarkdownPage - { - $key = base64_encode(self::class . 'byName' . $name); - return $this->cache->get( - $key, - fn () => $this->repo->byName($name), - 300 - ); - } -} -``` - -This simple wrapper just requires an EasyCache implementation and a MarkdownPageRepo in the constructor and uses them -to cache all queries for 5 minutes. The beauty is that we are not dependent on any implementation here, so we can switch -out the Repository or the Cache at any point down the road if we want to. - -In order to use that we need to update our `config/dependencies.php` to add an alias for the EasyCache interface as well -as defining our CachedMarkdownPageRepo as implementation for the MarkdownPageRepo interface: - -```php -MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r, -EasyCache::class => fn (ApcuCache $c) => $c, -``` - -If we try to access our webpage now, we are getting an error, as PHP-DI has detected a circular dependency that cannot -be autowired. - -The Problem is that our CachedMarkdownPageRepo ist defined as the implementation for the MarkdownPageRepo, but it also -requires that exact interface as a dependency. To resolve this issue we need to manually tell the container how to build -the CachedMarkdownPageRepo by adding another line to the `config/dependencies.php` file: - -```php -CachedMarkdownPageRepo::class => fn (EasyCache $c, FileSystemMarkdownPageRepo $r) => new CachedMarkdownPageRepo($c, $r), -``` - -Here we explicitly require the FileSystemMarkdownPageRepo and us that to create the CachedMarkdownPageRepo object. - -When you now navigate to the pages list or to a specific page the first load should take a while (because of our added delay) -but the following request should be answered blazingly fast. - -Before moving on to the next chapter we can take the caching approach even further, in the middleware chapter I talked -about a simple CachingMiddleware that caches all the GET-Request for some seconds, as they should not change that often, -and we can bypass most of our application logic if we just complelety cache away the responses our application generates, -and return them quite early in our Middleware-Pipeline befor the router gets called, or the invoker calls the action, -which itself uses some other services to fetch all the needed data. - -We will introduce a new `Middleware` namespace to place our `Cache.php` middleware: -```php -getMethod() !== 'GET') { - return $handler->handle($request); - } - $keyHash = base64_encode($request->getUri()->getPath()); - $result = $this->cache->get( - $keyHash, - fn () => Serializer::toString($handler->handle($request)), - 300 - ); - return Serializer::fromString($result); - } -} -``` - -The code is quite straight forward, but you might be confused by the Responseserializer I have added here, we need this -because the response body is a stream object, which doesnt always gets serialized correctly, therefore I use a class from -the laminas project to to all the heavy lifting for us. - -We need to add the now middleware to the `config/middlewares.php` file. - -```php ->](19-database.md) diff --git a/implementation/18-caching/phpstan-baseline.neon b/implementation/18-caching/phpstan-baseline.neon deleted file mode 100644 index 61697a1..0000000 --- a/implementation/18-caching/phpstan-baseline.neon +++ /dev/null @@ -1,7 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Parameter \\#1 \\$callable of method Invoker\\\\InvokerInterface\\:\\:call\\(\\) expects array\\|\\(callable\\(\\)\\: mixed\\)\\|string, mixed given\\.$#" - count: 1 - path: src/Http/InvokerRoutedHandler.php - diff --git a/implementation/18-caching/phpstan.neon b/implementation/18-caching/phpstan.neon deleted file mode 100644 index 2eac45a..0000000 --- a/implementation/18-caching/phpstan.neon +++ /dev/null @@ -1,8 +0,0 @@ -includes: - - phpstan-baseline.neon - -parameters: - level: max - paths: - - src - - config \ No newline at end of file diff --git a/implementation/18-caching/public/css/spectre-exp.min.css b/implementation/18-caching/public/css/spectre-exp.min.css deleted file mode 100644 index d313774..0000000 --- a/implementation/18-caching/public/css/spectre-exp.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/implementation/18-caching/public/css/spectre-icons.min.css b/implementation/18-caching/public/css/spectre-icons.min.css deleted file mode 100644 index 0276f7b..0000000 --- a/implementation/18-caching/public/css/spectre-icons.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/implementation/18-caching/public/css/spectre.min.css b/implementation/18-caching/public/css/spectre.min.css deleted file mode 100644 index 0fe23d9..0000000 --- a/implementation/18-caching/public/css/spectre.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/implementation/18-caching/public/favicon.ico b/implementation/18-caching/public/favicon.ico deleted file mode 100644 index 09499b8b3b3201e0f50088e3ac42e167778d1153..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/18-caching/public/index.php b/implementation/18-caching/public/index.php deleted file mode 100644 index 32f5eb3..0000000 --- a/implementation/18-caching/public/index.php +++ /dev/null @@ -1,5 +0,0 @@ -getBody(); - $data = [ - 'now' => $now()->format('H:i:s'), - 'name' => $name, - ]; - - $content = $renderer->render('hello', $data); - - $body->write($content); - - return $response - ->withStatus(200) - ->withBody($body); - } -} diff --git a/implementation/18-caching/src/Action/Other.php b/implementation/18-caching/src/Action/Other.php deleted file mode 100644 index da9ceaf..0000000 --- a/implementation/18-caching/src/Action/Other.php +++ /dev/null @@ -1,16 +0,0 @@ -parse('This *works* **too!**'); - $response->getBody()->write($html); - return $response->withStatus(200); - } -} diff --git a/implementation/18-caching/src/Action/Page.php b/implementation/18-caching/src/Action/Page.php deleted file mode 100644 index 96696e4..0000000 --- a/implementation/18-caching/src/Action/Page.php +++ /dev/null @@ -1,60 +0,0 @@ -repo->byName($page); - - // fix the next and previous buttons to work with our routing - $content = preg_replace('/\(\d\d-/m', '(', $page->content); - assert(is_string($content)); - $content = str_replace('.md)', ')', $content); - - $data = [ - 'title' => $page->title, - 'content' => $this->parser->parse($content), - ]; - - $html = $this->renderer->render('page/show', $data); - $this->response->getBody()->write($html); - return $this->response; - } - - public function list(): ResponseInterface - { - $pages = array_map(function (MarkdownPage $page) { - return [ - 'id' => $page->id, - 'title' => $page->title, - ]; - }, $this->repo->all()); - - $html = $this->renderer->render('page/list', ['pages' => $pages]); - $this->response->getBody()->write($html); - return $this->response; - } -} diff --git a/implementation/18-caching/src/Bootstrap.php b/implementation/18-caching/src/Bootstrap.php deleted file mode 100644 index 3abc2e5..0000000 --- a/implementation/18-caching/src/Bootstrap.php +++ /dev/null @@ -1,40 +0,0 @@ -getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), (int) $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); - -$app = $container->get(Kernel::class); -assert($app instanceof Kernel); - -$app->run(); diff --git a/implementation/18-caching/src/Exception/InternalServerError.php b/implementation/18-caching/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/implementation/18-caching/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ -factory::fromGlobals(); - } - - /** - * @param UriInterface|string $uri - * @param array $serverParams - */ - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { - return $this->factory->createServerRequest($method, $uri, $serverParams); - } -} diff --git a/implementation/18-caching/src/Factory/FileSystemSettingsProvider.php b/implementation/18-caching/src/Factory/FileSystemSettingsProvider.php deleted file mode 100644 index f071078..0000000 --- a/implementation/18-caching/src/Factory/FileSystemSettingsProvider.php +++ /dev/null @@ -1,22 +0,0 @@ -filePath; - assert($settings instanceof Settings); - return $settings; - } -} diff --git a/implementation/18-caching/src/Factory/PipelineProvider.php b/implementation/18-caching/src/Factory/PipelineProvider.php deleted file mode 100644 index 77738f8..0000000 --- a/implementation/18-caching/src/Factory/PipelineProvider.php +++ /dev/null @@ -1,25 +0,0 @@ -settings->middlewaresFile; - return new ContainerPipeline($middlewares, $this->tip, $this->container); - } -} diff --git a/implementation/18-caching/src/Factory/RequestFactory.php b/implementation/18-caching/src/Factory/RequestFactory.php deleted file mode 100644 index 2b17abc..0000000 --- a/implementation/18-caching/src/Factory/RequestFactory.php +++ /dev/null @@ -1,11 +0,0 @@ -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = $settings; - $builder->addDefinitions($dependencies); - // $builder->enableCompilation('/tmp'); - return $builder->build(); - } -} diff --git a/implementation/18-caching/src/Factory/SettingsProvider.php b/implementation/18-caching/src/Factory/SettingsProvider.php deleted file mode 100644 index ce1c5f0..0000000 --- a/implementation/18-caching/src/Factory/SettingsProvider.php +++ /dev/null @@ -1,10 +0,0 @@ -getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } - } - - $statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ); - header($statusLine, true, $response->getStatusCode()); - - if ($withoutBody) { - return; - } - - echo $response->getBody(); - } -} diff --git a/implementation/18-caching/src/Http/ContainerPipeline.php b/implementation/18-caching/src/Http/ContainerPipeline.php deleted file mode 100644 index 816cedd..0000000 --- a/implementation/18-caching/src/Http/ContainerPipeline.php +++ /dev/null @@ -1,82 +0,0 @@ - $middlewares - * @param RequestHandlerInterface $tip - * @param ContainerInterface $container - */ - public function __construct( - private array $middlewares, - private RequestHandlerInterface $tip, - private ContainerInterface $container, - ) { - } - - public function dispatch(ServerRequestInterface $request): ResponseInterface - { - $this->buildStack(); - return $this->tip->handle($request); - } - - private function buildStack(): void - { - foreach (array_reverse($this->middlewares) as $middleware) { - $next = $this->tip; - if ($middleware instanceof MiddlewareInterface) { - $this->tip = $this->wrapMiddleware($middleware, $next); - } - if (is_string($middleware)) { - $this->tip = $this->wrapResolvedMiddleware($middleware, $next); - } - } - } - - private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next, $this->container) implements RequestHandlerInterface { - public function __construct( - private readonly string $middleware, - private readonly RequestHandlerInterface $handler, - private readonly ContainerInterface $container, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = $this->container->get($this->middleware); - assert($middleware instanceof MiddlewareInterface); - return $middleware->process($request, $this->handler); - } - }; - } - - private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface - { - return new class ($middleware, $next) implements RequestHandlerInterface { - public function __construct( - private readonly MiddlewareInterface $middleware, - private readonly RequestHandlerInterface $handler, - ) { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } - }; - } -} diff --git a/implementation/18-caching/src/Http/Emitter.php b/implementation/18-caching/src/Http/Emitter.php deleted file mode 100644 index ce4c035..0000000 --- a/implementation/18-caching/src/Http/Emitter.php +++ /dev/null @@ -1,10 +0,0 @@ -getAttribute($this->routeAttributeName, false); - $vars = $request->getAttributes(); - $vars['request'] = $request; - $response = $this->invoker->call($handler, $vars); - if (! $response instanceof ResponseInterface) { - throw new InternalServerError('Handler returned invalid response'); - } - return $response; - } - - public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void - { - $this->routeAttributeName = $routeAttributeName; - } -} diff --git a/implementation/18-caching/src/Http/Pipeline.php b/implementation/18-caching/src/Http/Pipeline.php deleted file mode 100644 index 1a9dcda..0000000 --- a/implementation/18-caching/src/Http/Pipeline.php +++ /dev/null @@ -1,11 +0,0 @@ -dispatcher->dispatch( - $request->getMethod(), - $request->getUri()->getPath(), - ); - - if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new MethodNotAllowed; - } - - if ($routeInfo[0] === Dispatcher::FOUND) { - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - return $request->withAttribute( - $this->routeAttributeName, - $routeInfo[1] - ); - } - - throw new NotFound; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - try { - $request = $this->decorateRequest($request); - } catch (NotFound) { - $response = $this->responseFactory->createResponse(404); - $response->getBody()->write('Not Found'); - return $response; - } catch (MethodNotAllowed) { - return $this->responseFactory->createResponse(405); - } catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); - } - - if ($handler instanceof RoutedRequestHandler) { - $handler->setRouteAttributeName($this->routeAttributeName); - } - return $handler->handle($request); - } -} diff --git a/implementation/18-caching/src/Http/RoutedRequestHandler.php b/implementation/18-caching/src/Http/RoutedRequestHandler.php deleted file mode 100644 index a7407c9..0000000 --- a/implementation/18-caching/src/Http/RoutedRequestHandler.php +++ /dev/null @@ -1,10 +0,0 @@ -pipeline->dispatch($request); - } - - public function run(): void - { - $request = $this->requestFactory->fromGlobals(); - $response = $this->handle($request); - $this->emitter->emit($response); - } -} diff --git a/implementation/18-caching/src/Middleware/Cache.php b/implementation/18-caching/src/Middleware/Cache.php deleted file mode 100644 index 8460761..0000000 --- a/implementation/18-caching/src/Middleware/Cache.php +++ /dev/null @@ -1,38 +0,0 @@ -getMethod() !== 'GET') { - return $handler->handle($request); - } - $keyHash = base64_encode($request->getUri()->getPath()); - $result = $this->cache->get( - $keyHash, - fn () => $this->serializer::toString($handler->handle($request)), - 300 - ); - assert(is_string($result)); - return $this->serializer::fromString($result); - } -} diff --git a/implementation/18-caching/src/Model/MarkdownPage.php b/implementation/18-caching/src/Model/MarkdownPage.php deleted file mode 100644 index df244fd..0000000 --- a/implementation/18-caching/src/Model/MarkdownPage.php +++ /dev/null @@ -1,13 +0,0 @@ -cache->get( - $key, - fn () => $this->repo->all(), - 300 - ); - assert(is_array($result)); - foreach ($result as $page) { - assert($page instanceof MarkdownPage); - } - return $result; - } - - public function byName(string $name): MarkdownPage - { - $key = base64_encode(self::class . 'byName' . $name); - $result = $this->cache->get( - $key, - fn () => $this->repo->byName($name), - 300 - ); - assert($result instanceof MarkdownPage); - return $result; - } -} diff --git a/implementation/18-caching/src/Repository/FileSystemMarkdownPageRepo.php b/implementation/18-caching/src/Repository/FileSystemMarkdownPageRepo.php deleted file mode 100644 index cca350e..0000000 --- a/implementation/18-caching/src/Repository/FileSystemMarkdownPageRepo.php +++ /dev/null @@ -1,61 +0,0 @@ -dataPath . '*.md'); - if ($files === false) { - throw new InternalServerError('cannot read pages'); - } - return array_map(function (string $filename) { - $content = file_get_contents($filename); - if ($content === false) { - throw new InternalServerError('cannot read pages'); - } - $idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename); - return new MarkdownPage( - (int) substr($idAndTitle, 0, 2), - substr($idAndTitle, 3), - $content - ); - }, $files); - } - - public function byName(string $name): MarkdownPage - { - $pages = array_values( - array_filter( - $this->all(), - fn (MarkdownPage $p) => $p->title === $name, - ) - ); - - if (count($pages) !== 1) { - throw new NotFound; - } - - return $pages[0]; - } -} diff --git a/implementation/18-caching/src/Repository/MarkdownPageRepo.php b/implementation/18-caching/src/Repository/MarkdownPageRepo.php deleted file mode 100644 index 0792d32..0000000 --- a/implementation/18-caching/src/Repository/MarkdownPageRepo.php +++ /dev/null @@ -1,15 +0,0 @@ -engine->render($template, $data); - } -} diff --git a/implementation/18-caching/src/Template/ParsedownParser.php b/implementation/18-caching/src/Template/ParsedownParser.php deleted file mode 100644 index 2ffd287..0000000 --- a/implementation/18-caching/src/Template/ParsedownParser.php +++ /dev/null @@ -1,17 +0,0 @@ -parser->parse($markdown); - } -} diff --git a/implementation/18-caching/src/Template/Renderer.php b/implementation/18-caching/src/Template/Renderer.php deleted file mode 100644 index ff916ed..0000000 --- a/implementation/18-caching/src/Template/Renderer.php +++ /dev/null @@ -1,11 +0,0 @@ - $data - */ - public function render(string $template, array $data = []): string; -} diff --git a/implementation/18-caching/templates/hello.html b/implementation/18-caching/templates/hello.html deleted file mode 100644 index 15a4cd2..0000000 --- a/implementation/18-caching/templates/hello.html +++ /dev/null @@ -1,6 +0,0 @@ -{{> partials/head }} -
-

Hello {{name}}

-

The time is {{now}}

-
-{{> partials/foot }} diff --git a/implementation/18-caching/templates/page.html b/implementation/18-caching/templates/page.html deleted file mode 100644 index c3c5284..0000000 --- a/implementation/18-caching/templates/page.html +++ /dev/null @@ -1,5 +0,0 @@ -{{> partials/head }} -
- {{{content}}} -
-{{> partials/foot }} diff --git a/implementation/18-caching/templates/page/list.html b/implementation/18-caching/templates/page/list.html deleted file mode 100644 index bf42348..0000000 --- a/implementation/18-caching/templates/page/list.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Pages - - - -
- -
- - \ No newline at end of file diff --git a/implementation/18-caching/templates/page/show.html b/implementation/18-caching/templates/page/show.html deleted file mode 100644 index abe295e..0000000 --- a/implementation/18-caching/templates/page/show.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - {{title}} - - - - - - -
- {{{content}}} -
- - \ No newline at end of file diff --git a/implementation/18-caching/templates/pagelist.html b/implementation/18-caching/templates/pagelist.html deleted file mode 100644 index 538e2c4..0000000 --- a/implementation/18-caching/templates/pagelist.html +++ /dev/null @@ -1,11 +0,0 @@ -{{> partials/head }} -
- -
-{{> partials/foot }} diff --git a/implementation/18-caching/templates/partials/foot.html b/implementation/18-caching/templates/partials/foot.html deleted file mode 100644 index 17c7245..0000000 --- a/implementation/18-caching/templates/partials/foot.html +++ /dev/null @@ -1,3 +0,0 @@ -
- - \ No newline at end of file diff --git a/implementation/18-caching/templates/partials/head.html b/implementation/18-caching/templates/partials/head.html deleted file mode 100644 index 421d387..0000000 --- a/implementation/18-caching/templates/partials/head.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - No Framework: {{title}} - - - - - -
diff --git a/to-be-continued.md b/to-be-continued.md deleted file mode 100644 index a13a0fe..0000000 --- a/to-be-continued.md +++ /dev/null @@ -1,17 +0,0 @@ -### To be continued... - -Congratulations. You made it this far. - -I hope you were following the tutorial step by step and not just skipping over the chapters :) - -If you got something out of the tutorial I would appreciate a star. It's the only way for me to see if people are actually reading this :) - -Because this tutorial was so well-received, it inspired me to write a book. The book is a much more up to date version of this tutorial and covers a lot more. Click the link below to check it out (there is also a sample chapter available). - -### [Professional PHP: Building maintainable and secure applications](http://patricklouys.com/professional-php/) - -![](http://patricklouys.com/img/professional-php-thumb.png) - -Thanks for your time, - -Patrick From 3a402139b50bf343a06652ad233e1eb4cdeb0016 Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 25 Apr 2022 15:19:41 +0200 Subject: [PATCH 282/314] Changing port to 1235 to not clash with smtp default port --- 01-front-controller.md | 2 +- 04-development-helpers.md | 2 +- 07-dispatching-to-a-class.md | 2 +- 10-invoker.md | 2 +- 15-adding-content.md | 2 +- README.md | 2 +- Vagrantfile | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/01-front-controller.md b/01-front-controller.md index 87a12ad..53d17ef 100644 --- a/01-front-controller.md +++ b/01-front-controller.md @@ -38,7 +38,7 @@ Now navigate inside your `src` folder and create a new `Bootstrap.php` file with echo 'Hello World!'; ``` -Now let's see if everything is set up correctly. Open up a console and navigate into your projects `public` folder. In there type `php -S 0.0.0.0:1234` and press enter. This will start the built-in webserver and you can access your page in a browser with `http://localhost:1234`. You should now see the 'hello world' message. +Now let's see if everything is set up correctly. Open up a console and navigate into your projects `public` folder. In there type `php -S 0.0.0.0:1235` and press enter. This will start the built-in webserver and you can access your page in a browser with `http://localhost:1235`. You should now see the 'hello world' message. If there is an error, go back and try to fix it. If you only see a blank page, check the console window where the server is running for errors. diff --git a/04-development-helpers.md b/04-development-helpers.md index 74f913c..2dd943e 100644 --- a/04-development-helpers.md +++ b/04-development-helpers.md @@ -238,7 +238,7 @@ with lots of parameters by hand all the time, so i added a few lines to my compo ```json "scripts": { - "serve": "php -S 0.0.0.0:1234 -t public", + "serve": "php -S 0.0.0.0:1235 -t public", "phpstan": "./vendor/bin/phpstan analyze", "baseline": "./vendor/bin/phpstan analyze --generate-baseline", "check": "./vendor/bin/phpcs", diff --git a/07-dispatching-to-a-class.md b/07-dispatching-to-a-class.md index 0c961a4..c3555ef 100644 --- a/07-dispatching-to-a-class.md +++ b/07-dispatching-to-a-class.md @@ -72,7 +72,7 @@ case \FastRoute\Dispatcher::FOUND: So instead of just calling a method you are now instantiating an object and then calling the method on it. -Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug. +Now if you visit `http://localhost:1235/` everything should work. If not, go back and debug. And of course don't forget to commit your changes. diff --git a/10-invoker.md b/10-invoker.md index 3033fae..0ed6b59 100644 --- a/10-invoker.md +++ b/10-invoker.md @@ -65,7 +65,7 @@ $args['request'] = $request; $response = $container->call($handler, $args); ``` -Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'. +Try to open [localhost:1235/](http://localhost:1235/) in your browser and check if you are getting redirected to '/hello'. But by now you should know that I do not like to depend on specific implementations and the call method is not defined in the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container diff --git a/15-adding-content.md b/15-adding-content.md index 64562fa..0e85af0 100644 --- a/15-adding-content.md +++ b/15-adding-content.md @@ -241,7 +241,7 @@ class Page } ``` -You can now navigate your Browser to [localhost:1234/page][http://localhost:1234/page] and try out if everything works. +You can now navigate your Browser to [localhost:1235/page][http://localhost:1235/page] and try out if everything works. Of course this code is far from looking good. We heavily rely on the pages being files in the filesystem, and the action should never be aware of the filesystem in the first place, also we have a lot of string replacements and other repetetive diff --git a/README.md b/README.md index a636596..a072b20 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ vagrant ssh cd app ``` -I have exposed the port 1234 to be used in the VM, if you would like to use another one you are free to modify the +I have exposed the port 1235 to be used in the VM, if you would like to use another one you are free to modify the Vagrantfile. diff --git a/Vagrantfile b/Vagrantfile index 7cdc936..1509687 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -7,7 +7,7 @@ Vagrant.configure("2") do |config| v.memory = 2048 v.cpus = 4 end - config.vm.network "forwarded_port", guest: 1234, host: 1234 + config.vm.network "forwarded_port", guest: 1235, host: 1235 config.vm.network "forwarded_port", guest: 22, host: 2200, id: 'ssh' config.vm.synced_folder "./app", "/home/vagrant/app/" config.ssh.username = 'vagrant' From 33e7c0862470ea95093ffff1c57a41880da58fa1 Mon Sep 17 00:00:00 2001 From: lubiana Date: Sat, 30 Apr 2022 20:59:05 +0200 Subject: [PATCH 283/314] fix some typos and link to a blogpost about middleware pattern --- 14-middleware.md | 3 ++- README.md | 2 +- Vagrantfile | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/14-middleware.md b/14-middleware.md index 81f82a5..bddfead 100644 --- a/14-middleware.md +++ b/14-middleware.md @@ -7,7 +7,8 @@ a bit more about what this interface does and why it is used in many application The Middlewares are basically a number of wrappers that stand between the client and your application. Each request gets passed through all the middlewares, gets handled by our controllers and then the response gets passed back through all -the middlewars to the client/emitter. +the middlewars to the client/emitter. You can check out [this Blogpost](https://doeken.org/blog/middleware-pattern-in-php) +for a more in depth explanation of the middleware pattern. So every Middleware can modify the request before it goes on to the next middleware (and finally the handler) and the response after it gets created by our handlers. diff --git a/README.md b/README.md index a072b20..951a018 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ actually solve for you. This tutorial is based on the great [tutorial by Patrick Louys](https://github.com/PatrickLouys/no-framework-tutorial). My version is way more opiniated and uses some newer PHP features. But you should still check out his tutorial which is still very great and helped me personally a lot in taking the next step in my knowledge about PHP development. There is -also an [amazon book](https://patricklouys.com/professional-php/) which expands on the topics covered in this tutorial. +also an [amazing book](https://patricklouys.com/professional-php/) which expands on the topics covered in this tutorial. ## Getting started. diff --git a/Vagrantfile b/Vagrantfile index 1509687..dee2435 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -4,8 +4,8 @@ Vagrant.configure("2") do |config| config.vm.box = "archlinux/archlinux" config.vm.provider "virtualbox" do |v| - v.memory = 2048 - v.cpus = 4 + v.memory = 256 + v.cpus = 2 end config.vm.network "forwarded_port", guest: 1235, host: 1235 config.vm.network "forwarded_port", guest: 22, host: 2200, id: 'ssh' From 9954be75f815ff7fdd2cc15014a432b15ddb41d8 Mon Sep 17 00:00:00 2001 From: lubiana Date: Sat, 30 Apr 2022 21:24:25 +0200 Subject: [PATCH 284/314] some more typo and readability fixes --- 03-error-handler.md | 2 +- 04-development-helpers.md | 19 +++++++++---------- 06-router.md | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/03-error-handler.md b/03-error-handler.md index 60465d0..d1d7c06 100644 --- a/03-error-handler.md +++ b/03-error-handler.md @@ -58,7 +58,7 @@ $environment = getenv('ENVIRONMENT') ?: 'dev'; error_reporting(E_ALL); $whoops = new Run; -if ($environment == 'dev') { +if ($environment === 'dev') { $whoops->pushHandler(new PrettyPageHandler); } else { $whoops->pushHandler(function (\Throwable $e) { diff --git a/04-development-helpers.md b/04-development-helpers.md index 2dd943e..4469d69 100644 --- a/04-development-helpers.md +++ b/04-development-helpers.md @@ -47,7 +47,7 @@ Line Bootstrap.php The second error is something that "declare strict-types" already catches for us, but the first error is something that we usually would not discover easily without speccially looking for this errortype. -We can add a simple configfile called phpstan.neon to our project so that we do not have to specify the errorlevel and +We can add a simple configfile called `phpstan.neon` to our project so that we do not have to specify the errorlevel and path everytime we want to check our code for errors: ```yaml @@ -90,16 +90,15 @@ on an old legacy codebase and wanted to add static analysis to it but cant becau everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we are adding to our code. -In order to use that we have to add an empty file 'phpstan-baseline.neon' to our project, include that in the -phpstan.neon file and run phpstan with the -'--generate-baseline' option: +In order to use that we have to add an empty file `phpstan-baseline.neon` to our project, include that in the +`phpstan.neon` file and run phpstan with the `--generate-baseline` option: ```yaml includes: - phpstan-baseline.neon parameters: - level: 9 + level: max paths: - src ``` @@ -127,7 +126,7 @@ directory. You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup -a configuration file that i use in all my projects .php-cs-fixer.php: +a configuration file that i use in all my projects `.php-cs-fixer.php`: ```php @@ -233,8 +232,8 @@ you could just write dd($whoops) somewhere in your bootstrap.php to check how th #### Composer scripts -now we have a few commands that are available on the command line. i personally do not like to type complex commands -with lots of parameters by hand all the time, so i added a few lines to my composer.json: +now we have a few commands that are available on the command line. I personally do not like to type complex commands +with lots of parameters by hand all the time, so I added a few lines to my `composer.json`: ```json "scripts": { diff --git a/06-router.md b/06-router.md index 6c39ae5..10a0c93 100644 --- a/06-router.md +++ b/06-router.md @@ -86,7 +86,7 @@ return function(\FastRoute\RouteCollector $r) { }; ``` -Now let's rewrite the route dispatcher part to use the `Routes.php` file. +Now let's rewrite the route dispatcher part to use the `routes.php` file. ```php $routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; From 48dd6293c222f521ff0fa78394592d035437a3b5 Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 2 May 2022 08:38:14 +0200 Subject: [PATCH 285/314] disable composer timeout in development helpers chapter --- 04-development-helpers.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/04-development-helpers.md b/04-development-helpers.md index 4469d69..4979ca0 100644 --- a/04-development-helpers.md +++ b/04-development-helpers.md @@ -237,7 +237,10 @@ with lots of parameters by hand all the time, so I added a few lines to my `comp ```json "scripts": { - "serve": "php -S 0.0.0.0:1235 -t public", + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], "phpstan": "./vendor/bin/phpstan analyze", "baseline": "./vendor/bin/phpstan analyze --generate-baseline", "check": "./vendor/bin/phpcs", @@ -245,8 +248,8 @@ with lots of parameters by hand all the time, so I added a few lines to my `comp }, ``` -that way i can just type "composer" followed by the command name in the root of my project. if i want to start the -php devserver i can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the +that way I can just type "composer" followed by the command name in the root of my project. if i want to start the +php devserver I can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the time. You could also configure PhpStorm to automatically run these commands in the background and highlight the violations From 6b059079f56b7934c05d8e7a9b92cffffc196e8a Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 2 May 2022 08:39:48 +0200 Subject: [PATCH 286/314] readability fixes in chapters 7 and 9 --- 07-dispatching-to-a-class.md | 4 ++-- 09-dependency-injector.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/07-dispatching-to-a-class.md b/07-dispatching-to-a-class.md index c3555ef..830a30f 100644 --- a/07-dispatching-to-a-class.md +++ b/07-dispatching-to-a-class.md @@ -12,7 +12,7 @@ So forget about MVC and instead let's worry about [separation of concerns](http: We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other common names are 'Controllers' or 'Actions'. -Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes. +Create a new folder inside the `src/` folder with the name `Action`. In this folder we will place all our action classes. In there, create a `Hello.php` file. ```php @@ -36,7 +36,7 @@ You can see that we implement the [RequestHandlerInterface](https://github.com/p that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement it in some other parts of our application as well. In order to use that Interface we have to require it with composer: -'composer require psr/http-server-handler'. +`composer require psr/http-server-handler`. The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name. At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder. diff --git a/09-dependency-injector.md b/09-dependency-injector.md index 7f7c6a2..cfbeb7a 100644 --- a/09-dependency-injector.md +++ b/09-dependency-injector.md @@ -12,7 +12,7 @@ for a [suitable solution on packagist](https://packagist.org/providers/psr/conta I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) out of the box. -After installing the container through composer create a new file with the name 'dependencies.php' in your config folder: +After installing the container through composer create a new file with the name `dependencies.php` in your config folder: ```php Date: Mon, 2 May 2022 08:58:41 +0200 Subject: [PATCH 287/314] bump memory in vagrantfile to 512mb --- Vagrantfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Vagrantfile b/Vagrantfile index dee2435..fb02d72 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -4,7 +4,7 @@ Vagrant.configure("2") do |config| config.vm.box = "archlinux/archlinux" config.vm.provider "virtualbox" do |v| - v.memory = 256 + v.memory = 512 v.cpus = 2 end config.vm.network "forwarded_port", guest: 1235, host: 1235 From 876448844975743cbd5983123b80202724d20a4e Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 2 May 2022 14:36:41 +0200 Subject: [PATCH 288/314] Update author name in composer chapter --- 02-composer.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/02-composer.md b/02-composer.md index a25a4a8..6bd74e5 100644 --- a/02-composer.md +++ b/02-composer.md @@ -28,8 +28,8 @@ Add the following content to the file: }, "authors": [ { - "name": "lubiana", - "email": "lubiana@hannover.ccc.de" + "name": "example", + "email": "test@example.com" } ] } From ec083b8de9f79f29403d8101105751dc3787b90a Mon Sep 17 00:00:00 2001 From: lubiana Date: Fri, 13 May 2022 14:52:56 +0200 Subject: [PATCH 289/314] update devhelpers to use ecs instead of phpcs and php-cs-fixer --- 04-development-helpers.md | 141 +++++++++++++++++++++++++------------- 1 file changed, 94 insertions(+), 47 deletions(-) diff --git a/04-development-helpers.md b/04-development-helpers.md index 4979ca0..01f6e62 100644 --- a/04-development-helpers.md +++ b/04-development-helpers.md @@ -7,7 +7,7 @@ used only for development they should not be used in a production environment. C file called "dev-dependencies", everything that is required in this section does not get installen in production. Let's install our dev-helpers and i will explain them one by one: -`composer require --dev phpstan/phpstan php-cs-fixer/shim symfony/var-dumper squizlabs/php_codesniffer` +`composer require --dev phpstan/phpstan symfony/var-dumper slevomat/coding-standard symplify/easy-coding-standard` #### Static Code Analysis with phpstan @@ -116,57 +116,104 @@ Note: Using configuration file /home/vagrant/app/phpstan.neon. you can read more about the possible parameters and usage options in the [documentation](https://phpstan.org/user-guide/getting-started) -#### PHP-CS-Fixer +#### Easy-Coding-Standard -Another great tool is the php-cs-fixer, which just applies a specific style to your code. +There are two great tools that help us with applying a consistent coding style to our project as well as check and +automatically fix some other errors and oversights that we might not bother with when writing our code. -when you run `./vendor/bin/php-cs-fixer fix ./` it applies the psr-12 code style to every php file in you current -directory. +The first one is [PHP Coding Standards Fixer](https://cs.symfony.com/) which can automatically detect violations of +a defined coding standard and fix them. The second tool is [PHP CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) +which basically does the same has in my experience some more Rules available that we can apply to our code. -You can read more about its usage and possible rulesets in the [documentation](https://github.com/FriendsOfPHP/PHP-CS-Fixer#documentation) - -personally i like to have a more opiniated version with some rules added to the psr-12 standard and have therefore setup -a configuration file that i use in all my projects `.php-cs-fixer.php`: +But we are going to use neither of those tools directly and instead choose the [Easy Coding Standard](https://github.com/symplify/easy-coding-standard) +which allows us to combine rules from both mentioned tools, and also claims to run much faster. You could check out the +documentation and decide on your own coding standard. Or use the one provided by me, which is base on PSR-12 but adds +some highly opiniated options. First create a file 'ecs.php' and either add your own configuration or copy the my +prepared one: ```php setRiskyAllowed(true) - ->setRules([ - '@PSR12:risky' => true, - '@PSR12' => true, - '@PHP80Migration' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, - 'array_indentation' => true, - 'include' => true, - 'blank_line_after_opening_tag' => false, - 'native_constant_invocation' => true, - 'new_with_braces' => false, - 'native_function_invocation' => [ - 'include' => ['@all'] - ], - 'no_unused_imports' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'ordered_interfaces' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - ]) - ); + +use PhpCsFixer\Fixer\Import\OrderedImportsFixer; +use PhpCsFixer\Fixer\PhpTag\BlankLineAfterOpeningTagFixer; +use SlevomatCodingStandard\Sniffs\Classes\ClassConstantVisibilitySniff; +use SlevomatCodingStandard\Sniffs\Namespaces\AlphabeticallySortedUsesSniff; +use SlevomatCodingStandard\Sniffs\Namespaces\DisallowGroupUseSniff; +use SlevomatCodingStandard\Sniffs\Namespaces\MultipleUsesPerLineSniff; +use SlevomatCodingStandard\Sniffs\Namespaces\NamespaceSpacingSniff; +use SlevomatCodingStandard\Sniffs\Namespaces\ReferenceUsedNamesOnlySniff; +use SlevomatCodingStandard\Sniffs\Namespaces\UseSpacingSniff; +use SlevomatCodingStandard\Sniffs\TypeHints\DeclareStrictTypesSniff; +use SlevomatCodingStandard\Sniffs\TypeHints\UnionTypeHintFormatSniff; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use Symplify\EasyCodingStandard\ValueObject\Option; +use Symplify\EasyCodingStandard\ValueObject\Set\SetList; + +return static function (ContainerConfigurator $containerConfigurator): void { + $parameters = $containerConfigurator->parameters(); + $parameters->set(Option::PATHS, [__DIR__ . '/src', __DIR__ . '/ecs.php']); + $parameters->set(Option::PARALLEL, true); + $parameters->set(Option::SKIP, [BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class]); + + $containerConfigurator->import(SetList::PSR_12); + $containerConfigurator->import(SetList::STRICT); + $containerConfigurator->import(SetList::ARRAY); + $containerConfigurator->import(SetList::SPACES); + $containerConfigurator->import(SetList::DOCBLOCK); + $containerConfigurator->import(SetList::CLEAN_CODE); + $containerConfigurator->import(SetList::COMMON); + $containerConfigurator->import(SetList::COMMENTS); + $containerConfigurator->import(SetList::NAMESPACES); + $containerConfigurator->import(SetList::SYMPLIFY); + $containerConfigurator->import(SetList::CONTROL_STRUCTURES); + + $services = $containerConfigurator->services(); + + // force visibitily declaration on class constants + $services->set(ClassConstantVisibilitySniff::class) + ->property('fixable', true); + + // sort all use statements + $services->set(AlphabeticallySortedUsesSniff::class); + + $services->set(DisallowGroupUseSniff::class); + $services->set(MultipleUsesPerLineSniff::class); + $services->set(NamespaceSpacingSniff::class); + + // import all namespaces, and event php core functions and classes + $services->set(ReferenceUsedNamesOnlySniff::class) + ->property('allowFallbackGlobalConstants', false) + ->property('allowFallbackGlobalFunctions', false) + ->property('allowFullyQualifiedGlobalClasses', false) + ->property('allowFullyQualifiedGlobalConstants', false) + ->property('allowFullyQualifiedGlobalFunctions', false) + ->property('allowFullyQualifiedNameForCollidingClasses', true) + ->property('allowFullyQualifiedNameForCollidingConstants', true) + ->property('allowFullyQualifiedNameForCollidingFunctions', true) + ->property('searchAnnotations', true) + ->property('fixable', true); + + // define newlines between use statements + $services->set(UseSpacingSniff::class) + ->property('linesCountBeforeFirstUse', 1) + ->property('linesCountBetweenUseTypes', 1) + ->property('linesCountAfterLastUse', 1); + + // strict types declaration should be on same line as opening tag + $services->set(DeclareStrictTypesSniff::class) + ->property('declareOnFirstLine', true) + ->property('spacesCountAroundEqualsSign', 0); + + // disallow ?Foo typehint in favor of Foo|null + $services->set(UnionTypeHintFormatSniff::class) + ->property('withSpaces', 'no') + ->property('shortNullable', 'no') + ->property('nullPosition', 'last'); +}; + ``` +You can now use `./vendor/bin/ecs` to list all violations of the defined standard and `./vendor/bin/ecs --fix` to +automatically fix them. #### PHP Codesniffer @@ -223,7 +270,7 @@ You can then use `./vendor/bin/phpcbf` to try to fix them another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small functions. -dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects +dump(); is basically like phps var_dump() but has a better looking output that helps when looking into bigger objects or arrays. dd() on the other hand is a function that dumps its parameters and then exits the php-script. @@ -259,4 +306,4 @@ flow when programming and always forces me to be absolutely strict even if I am My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before commiting and pushing the code. -[<< previous](03-error-handler.md) | [next >>](05-http.md) +[<< previous](03-error-handler.md) | [next >>](05-http.md) \ No newline at end of file From d35e18468d02b41902a9a114bb4603ed52851897 Mon Sep 17 00:00:00 2001 From: lubiana Date: Wed, 18 May 2022 07:22:26 +0200 Subject: [PATCH 290/314] update development helpers chapter --- 04-development-helpers.md | 59 ++++----------------------------------- 1 file changed, 5 insertions(+), 54 deletions(-) diff --git a/04-development-helpers.md b/04-development-helpers.md index 01f6e62..0da4759 100644 --- a/04-development-helpers.md +++ b/04-development-helpers.md @@ -215,56 +215,6 @@ return static function (ContainerConfigurator $containerConfigurator): void { You can now use `./vendor/bin/ecs` to list all violations of the defined standard and `./vendor/bin/ecs --fix` to automatically fix them. -#### PHP Codesniffer - -The PHPCodesniffer is sort of a combination of the previous tools, it checks for a defined codingstyle and some extra -rules that are not just stylechanges but instead enforces extra rules in if-statements, exception handling etc. - -it provides the `phpcs` command to check for violations and the `phpcbf` command to actually fix most of the violations. - -Without configuration the tool tries to apply the PSR12 standard just like the php-cs-fixer, but as you might have -guessed we are adding some extra rules. - -Lets install the ruleset with composer -```shell -composer require --dev mnapoli/hard-mode -``` - -and add a configuration file to actually use it `.phpcs.xml.dist` -```xml - - - - - src - - - -``` - -running `./vendor/bin/phpcs` now checks our src directory for violations and gives us a detailed list about the findings. - -``` -[vagrant@archlinux app]$ ./vendor/bin/phpcs - -FILE: src/Bootstrap.php ----------------------------------------------------------------------------------------------------- -FOUND 4 ERRORS AFFECTING 4 LINES ----------------------------------------------------------------------------------------------------- - 7 | ERROR | [x] Use statements should be sorted alphabetically. The first wrong one is Throwable. - 8 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 11 | ERROR | [x] Expected 1 lines between different types of use statement, found 0. - 24 | ERROR | [x] String "ERROR: " does not require double quotes; use single quotes instead ----------------------------------------------------------------------------------------------------- -PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY ----------------------------------------------------------------------------------------------------- - -Time: 639ms; Memory: 10MB -``` - -You can then use `./vendor/bin/phpcbf` to try to fix them - - #### Symfony Var-Dumper another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small @@ -290,8 +240,8 @@ with lots of parameters by hand all the time, so I added a few lines to my `comp ], "phpstan": "./vendor/bin/phpstan analyze", "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/phpcs", - "fix": "./vendor/bin/php-cs-fixer fix && ./vendor/bin/phpcbf" + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix" }, ``` @@ -303,7 +253,8 @@ You could also configure PhpStorm to automatically run these commands in the bac directly in the file you are currently editing. I personally am not a fan of this approach because it often disrupts my flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. -My workflow is to just write my code the way i currently feel and that execute the phpstan and the fix scripts before -commiting and pushing the code. +My workflow is to just write my code the way I currently feel and that execute the phpstan and the fix scripts before +commiting and pushing the code. There is a [highly opiniated blogpost](https://tomasvotruba.com/blog/2019/06/24/do-you-use-php-codesniffer-and-php-cs-fixer-phpstorm-plugin-you-are-slow-and-expensive/) +discussing that topic further. That you can read. But in the end it boils down to what you are most comfortable with. [<< previous](03-error-handler.md) | [next >>](05-http.md) \ No newline at end of file From d2ebd6fd21e4ba7e4fc337cf80f7b08363ed42dd Mon Sep 17 00:00:00 2001 From: lubiana Date: Wed, 18 May 2022 07:40:45 +0200 Subject: [PATCH 291/314] add rector to dev helpers --- 04-development-helpers.md | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/04-development-helpers.md b/04-development-helpers.md index 0da4759..bde29e0 100644 --- a/04-development-helpers.md +++ b/04-development-helpers.md @@ -7,7 +7,7 @@ used only for development they should not be used in a production environment. C file called "dev-dependencies", everything that is required in this section does not get installen in production. Let's install our dev-helpers and i will explain them one by one: -`composer require --dev phpstan/phpstan symfony/var-dumper slevomat/coding-standard symplify/easy-coding-standard` +`composer require --dev phpstan/phpstan symfony/var-dumper slevomat/coding-standard symplify/easy-coding-standard rector/rector` #### Static Code Analysis with phpstan @@ -215,6 +215,37 @@ return static function (ContainerConfigurator $containerConfigurator): void { You can now use `./vendor/bin/ecs` to list all violations of the defined standard and `./vendor/bin/ecs --fix` to automatically fix them. +#### Rector + +The next tool helps us with automatic refactorings and upgrades to newer PHP versions. + +Place a file called `rector.php` in your app directory and put in the following content: + +```php +paths([__DIR__ . '/src', __DIR__ . '/config']); + + $rectorConfig->importNames(); + + $rectorConfig->sets([ + LevelSetList::UP_TO_PHP_81, + ]); +}; +``` + +This config fixes your code and replaces function call and constructs that are deprecated in modern php versions. This +includes all fixes from PHP 5.2 up to PHP 8.1. You can take a look at all the rules [here](https://github.com/rectorphp/rector/blob/main/docs/rector_rules_overview.md#php52). + +To run this tool simply type `./vendor/bin/rector process` in your console. This should not to much right now, but will +be quite useful when php 8.2 or newer versions are released. + #### Symfony Var-Dumper another great tool for some quick debugging without xdebug is the symfony var-dumper. This just gives us some small @@ -241,7 +272,8 @@ with lots of parameters by hand all the time, so I added a few lines to my `comp "phpstan": "./vendor/bin/phpstan analyze", "baseline": "./vendor/bin/phpstan analyze --generate-baseline", "check": "./vendor/bin/ecs", - "fix": "./vendor/bin/ecs --fix" + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" }, ``` From 6bf1c733705aba8f5aab82b08eee6805e84761f5 Mon Sep 17 00:00:00 2001 From: lubiana Date: Wed, 18 May 2022 08:32:23 +0200 Subject: [PATCH 292/314] update ecs config to newer version in devhelper chapter --- 04-development-helpers.md | 46 ++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/04-development-helpers.md b/04-development-helpers.md index bde29e0..b2aece8 100644 --- a/04-development-helpers.md +++ b/04-development-helpers.md @@ -135,8 +135,10 @@ prepared one: parameters(); +return static function (ECSConfig $config): void { + $parameters = $config->parameters(); $parameters->set(Option::PATHS, [__DIR__ . '/src', __DIR__ . '/ecs.php']); $parameters->set(Option::PARALLEL, true); - $parameters->set(Option::SKIP, [BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class]); + $parameters->set( + Option::SKIP, + [BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class] + ); - $containerConfigurator->import(SetList::PSR_12); - $containerConfigurator->import(SetList::STRICT); - $containerConfigurator->import(SetList::ARRAY); - $containerConfigurator->import(SetList::SPACES); - $containerConfigurator->import(SetList::DOCBLOCK); - $containerConfigurator->import(SetList::CLEAN_CODE); - $containerConfigurator->import(SetList::COMMON); - $containerConfigurator->import(SetList::COMMENTS); - $containerConfigurator->import(SetList::NAMESPACES); - $containerConfigurator->import(SetList::SYMPLIFY); - $containerConfigurator->import(SetList::CONTROL_STRUCTURES); + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); - $services = $containerConfigurator->services(); + $services = $config->services(); - // force visibitily declaration on class constants + // force visibility declaration on class constants $services->set(ClassConstantVisibilitySniff::class) ->property('fixable', true); // sort all use statements $services->set(AlphabeticallySortedUsesSniff::class); - $services->set(DisallowGroupUseSniff::class); $services->set(MultipleUsesPerLineSniff::class); $services->set(NamespaceSpacingSniff::class); @@ -209,8 +215,12 @@ return static function (ContainerConfigurator $containerConfigurator): void { ->property('withSpaces', 'no') ->property('shortNullable', 'no') ->property('nullPosition', 'last'); + + // Remove useless parantheses in new statements + $services->set(NewWithoutParenthesesSniff::class); }; + ``` You can now use `./vendor/bin/ecs` to list all violations of the defined standard and `./vendor/bin/ecs --fix` to automatically fix them. From 0580f100c2891f3e9650608153ade43752eea20e Mon Sep 17 00:00:00 2001 From: lubiana Date: Thu, 19 May 2022 23:34:22 +0200 Subject: [PATCH 293/314] enable intl extension --- Vagrantfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Vagrantfile b/Vagrantfile index fb02d72..c1df0b9 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -23,5 +23,6 @@ Vagrant.configure("2") do |config| echo -e 'zend.assertions=1\n' >> /etc/php/conf.d/tutorial.ini echo -e 'opcache.enable=1\nopcache.enable_cli=1\n' >> /etc/php/conf.d/tutorial.ini echo -e 'acp.enable=1\napc.enable_cli=1\n' >> /etc/php/conf.d/tutorial.ini + echo -e 'extension=intl\n' >> /etc/php/conf.d/tutorial.ini SHELL end From 45a071489383d5f34621c6a545727479a9df7989 Mon Sep 17 00:00:00 2001 From: lubiana Date: Thu, 19 May 2022 23:35:10 +0200 Subject: [PATCH 294/314] update ecs and rector config --- 04-development-helpers.md | 87 ++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/04-development-helpers.md b/04-development-helpers.md index b2aece8..b9fcd50 100644 --- a/04-development-helpers.md +++ b/04-development-helpers.md @@ -148,17 +148,12 @@ use SlevomatCodingStandard\Sniffs\Namespaces\UseSpacingSniff; use SlevomatCodingStandard\Sniffs\TypeHints\DeclareStrictTypesSniff; use SlevomatCodingStandard\Sniffs\TypeHints\UnionTypeHintFormatSniff; use Symplify\EasyCodingStandard\Config\ECSConfig; -use Symplify\EasyCodingStandard\ValueObject\Option; use Symplify\EasyCodingStandard\ValueObject\Set\SetList; return static function (ECSConfig $config): void { - $parameters = $config->parameters(); - $parameters->set(Option::PATHS, [__DIR__ . '/src', __DIR__ . '/ecs.php']); - $parameters->set(Option::PARALLEL, true); - $parameters->set( - Option::SKIP, - [BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class] - ); + $config->parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); $config->sets([ SetList::PSR_12, @@ -174,53 +169,59 @@ return static function (ECSConfig $config): void { SetList::CONTROL_STRUCTURES, ]); - $services = $config->services(); - // force visibility declaration on class constants - $services->set(ClassConstantVisibilitySniff::class) - ->property('fixable', true); + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); // sort all use statements - $services->set(AlphabeticallySortedUsesSniff::class); - $services->set(DisallowGroupUseSniff::class); - $services->set(MultipleUsesPerLineSniff::class); - $services->set(NamespaceSpacingSniff::class); + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); - // import all namespaces, and event php core functions and classes - $services->set(ReferenceUsedNamesOnlySniff::class) - ->property('allowFallbackGlobalConstants', false) - ->property('allowFallbackGlobalFunctions', false) - ->property('allowFullyQualifiedGlobalClasses', false) - ->property('allowFullyQualifiedGlobalConstants', false) - ->property('allowFullyQualifiedGlobalFunctions', false) - ->property('allowFullyQualifiedNameForCollidingClasses', true) - ->property('allowFullyQualifiedNameForCollidingConstants', true) - ->property('allowFullyQualifiedNameForCollidingFunctions', true) - ->property('searchAnnotations', true) - ->property('fixable', true); + // import all namespaces, and even php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); // define newlines between use statements - $services->set(UseSpacingSniff::class) - ->property('linesCountBeforeFirstUse', 1) - ->property('linesCountBetweenUseTypes', 1) - ->property('linesCountAfterLastUse', 1); + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); // strict types declaration should be on same line as opening tag - $services->set(DeclareStrictTypesSniff::class) - ->property('declareOnFirstLine', true) - ->property('spacesCountAroundEqualsSign', 0); + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); // disallow ?Foo typehint in favor of Foo|null - $services->set(UnionTypeHintFormatSniff::class) - ->property('withSpaces', 'no') - ->property('shortNullable', 'no') - ->property('nullPosition', 'last'); + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); - // Remove useless parantheses in new statements - $services->set(NewWithoutParenthesesSniff::class); + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); }; - ``` You can now use `./vendor/bin/ecs` to list all violations of the defined standard and `./vendor/bin/ecs --fix` to automatically fix them. @@ -240,7 +241,7 @@ use Rector\Config\RectorConfig; use Rector\Set\ValueObject\LevelSetList; return static function (RectorConfig $rectorConfig): void { - $rectorConfig->paths([__DIR__ . '/src', __DIR__ . '/config']); + $rectorConfig->paths([__DIR__ . '/src', __DIR__ . '/rector.php', __DIR__ . '/ecs.php']); $rectorConfig->importNames(); From b33f0565c9da000a0043a0949b8e9ce800cd0869 Mon Sep 17 00:00:00 2001 From: lubiana Date: Thu, 19 May 2022 23:35:51 +0200 Subject: [PATCH 295/314] fix wrong namespace in for laminas request in http chapter --- 05-http.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/05-http.md b/05-http.md index 6166214..61077a6 100644 --- a/05-http.md +++ b/05-http.md @@ -30,7 +30,7 @@ enter Now you can add the following below your error handler code in your `Bootstrap.php` (and don't forget to remove the exception): ```php -$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); +$request = \Laminas\Diactoros\ServerRequestFactory::fromGlobals(); $response = new \Laminas\Diactoros\Response; $response->getBody()->write('Hello World! '); $response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); From 1984efef7238f498317e7efaf911196ebe5bd659 Mon Sep 17 00:00:00 2001 From: lubiana Date: Thu, 19 May 2022 23:39:08 +0200 Subject: [PATCH 296/314] readd implementation folder --- implementation/03/composer.json | 18 + implementation/03/public/favicon.ico | Bin 0 -> 15086 bytes implementation/03/public/index.php | 3 + implementation/03/src/Bootstrap.php | 27 + implementation/04/composer.json | 45 + implementation/04/composer.lock | 872 ++++++++++++ implementation/04/ecs.php | 89 ++ implementation/04/phpstan-baseline.neon | 6 + implementation/04/phpstan.neon | 7 + implementation/04/public/favicon.ico | Bin 0 -> 15086 bytes implementation/04/public/index.php | 3 + implementation/04/rector.php | 11 + implementation/04/src/Bootstrap.php | 34 + implementation/05/composer.json | 46 + implementation/05/composer.lock | 1079 +++++++++++++++ implementation/05/ecs.php | 89 ++ implementation/05/phpstan-baseline.neon | 6 + implementation/05/phpstan.neon | 7 + implementation/05/public/favicon.ico | Bin 0 -> 15086 bytes implementation/05/public/index.php | 3 + implementation/05/rector.php | 11 + implementation/05/src/Bootstrap.php | 63 + implementation/06/composer.json | 47 + implementation/06/composer.lock | 1129 ++++++++++++++++ implementation/06/config/routes.php | 21 + implementation/06/ecs.php | 89 ++ implementation/06/phpstan-baseline.neon | 6 + implementation/06/phpstan.neon | 8 + implementation/06/public/favicon.ico | Bin 0 -> 15086 bytes implementation/06/public/index.php | 3 + implementation/06/rector.php | 12 + implementation/06/src/Bootstrap.php | 97 ++ implementation/07/composer.json | 48 + implementation/07/composer.lock | 1186 +++++++++++++++++ implementation/07/config/routes.php | 10 + implementation/07/ecs.php | 89 ++ implementation/07/phpstan-baseline.neon | 6 + implementation/07/phpstan.neon | 8 + implementation/07/public/favicon.ico | Bin 0 -> 15086 bytes implementation/07/public/index.php | 3 + implementation/07/rector.php | 12 + implementation/07/src/Action/Hello.php | 20 + implementation/07/src/Action/Other.php | 19 + implementation/07/src/Bootstrap.php | 101 ++ .../07/src/Exception/InternalServerError.php | 9 + .../07/src/Exception/MethodNotAllowed.php | 9 + implementation/07/src/Exception/NotFound.php | 9 + implementation/08/composer.json | 48 + implementation/08/composer.lock | 1186 +++++++++++++++++ implementation/08/config/routes.php | 10 + implementation/08/ecs.php | 89 ++ implementation/08/phpstan-baseline.neon | 6 + implementation/08/phpstan.neon | 8 + implementation/08/public/favicon.ico | Bin 0 -> 15086 bytes implementation/08/public/index.php | 3 + implementation/08/rector.php | 12 + implementation/08/src/Action/Hello.php | 25 + implementation/08/src/Action/Other.php | 24 + implementation/08/src/Bootstrap.php | 101 ++ .../08/src/Exception/InternalServerError.php | 9 + .../08/src/Exception/MethodNotAllowed.php | 9 + implementation/08/src/Exception/NotFound.php | 9 + implementation/09-wip/composer.json | 48 + implementation/09-wip/composer.lock | 1186 +++++++++++++++++ implementation/09-wip/config/container.php | 9 + implementation/09-wip/config/routes.php | 10 + implementation/09-wip/ecs.php | 89 ++ implementation/09-wip/phpstan-baseline.neon | 6 + implementation/09-wip/phpstan.neon | 8 + implementation/09-wip/public/favicon.ico | Bin 0 -> 15086 bytes implementation/09-wip/public/index.php | 3 + implementation/09-wip/rector.php | 12 + implementation/09-wip/src/Action/Hello.php | 29 + implementation/09-wip/src/Action/Other.php | 24 + implementation/09-wip/src/Bootstrap.php | 101 ++ .../src/Exception/InternalServerError.php | 9 + .../09-wip/src/Exception/MethodNotAllowed.php | 9 + .../09-wip/src/Exception/NotFound.php | 9 + .../09-wip/src/Service/Time/Clock.php | 8 + .../09-wip/src/Service/Time/SystemClock.php | 12 + 80 files changed, 8471 insertions(+) create mode 100644 implementation/03/composer.json create mode 100644 implementation/03/public/favicon.ico create mode 100644 implementation/03/public/index.php create mode 100644 implementation/03/src/Bootstrap.php create mode 100644 implementation/04/composer.json create mode 100644 implementation/04/composer.lock create mode 100644 implementation/04/ecs.php create mode 100644 implementation/04/phpstan-baseline.neon create mode 100644 implementation/04/phpstan.neon create mode 100644 implementation/04/public/favicon.ico create mode 100644 implementation/04/public/index.php create mode 100644 implementation/04/rector.php create mode 100644 implementation/04/src/Bootstrap.php create mode 100644 implementation/05/composer.json create mode 100644 implementation/05/composer.lock create mode 100644 implementation/05/ecs.php create mode 100644 implementation/05/phpstan-baseline.neon create mode 100644 implementation/05/phpstan.neon create mode 100644 implementation/05/public/favicon.ico create mode 100644 implementation/05/public/index.php create mode 100644 implementation/05/rector.php create mode 100644 implementation/05/src/Bootstrap.php create mode 100644 implementation/06/composer.json create mode 100644 implementation/06/composer.lock create mode 100644 implementation/06/config/routes.php create mode 100644 implementation/06/ecs.php create mode 100644 implementation/06/phpstan-baseline.neon create mode 100644 implementation/06/phpstan.neon create mode 100644 implementation/06/public/favicon.ico create mode 100644 implementation/06/public/index.php create mode 100644 implementation/06/rector.php create mode 100644 implementation/06/src/Bootstrap.php create mode 100644 implementation/07/composer.json create mode 100644 implementation/07/composer.lock create mode 100644 implementation/07/config/routes.php create mode 100644 implementation/07/ecs.php create mode 100644 implementation/07/phpstan-baseline.neon create mode 100644 implementation/07/phpstan.neon create mode 100644 implementation/07/public/favicon.ico create mode 100644 implementation/07/public/index.php create mode 100644 implementation/07/rector.php create mode 100644 implementation/07/src/Action/Hello.php create mode 100644 implementation/07/src/Action/Other.php create mode 100644 implementation/07/src/Bootstrap.php create mode 100644 implementation/07/src/Exception/InternalServerError.php create mode 100644 implementation/07/src/Exception/MethodNotAllowed.php create mode 100644 implementation/07/src/Exception/NotFound.php create mode 100644 implementation/08/composer.json create mode 100644 implementation/08/composer.lock create mode 100644 implementation/08/config/routes.php create mode 100644 implementation/08/ecs.php create mode 100644 implementation/08/phpstan-baseline.neon create mode 100644 implementation/08/phpstan.neon create mode 100644 implementation/08/public/favicon.ico create mode 100644 implementation/08/public/index.php create mode 100644 implementation/08/rector.php create mode 100644 implementation/08/src/Action/Hello.php create mode 100644 implementation/08/src/Action/Other.php create mode 100644 implementation/08/src/Bootstrap.php create mode 100644 implementation/08/src/Exception/InternalServerError.php create mode 100644 implementation/08/src/Exception/MethodNotAllowed.php create mode 100644 implementation/08/src/Exception/NotFound.php create mode 100644 implementation/09-wip/composer.json create mode 100644 implementation/09-wip/composer.lock create mode 100644 implementation/09-wip/config/container.php create mode 100644 implementation/09-wip/config/routes.php create mode 100644 implementation/09-wip/ecs.php create mode 100644 implementation/09-wip/phpstan-baseline.neon create mode 100644 implementation/09-wip/phpstan.neon create mode 100644 implementation/09-wip/public/favicon.ico create mode 100644 implementation/09-wip/public/index.php create mode 100644 implementation/09-wip/rector.php create mode 100644 implementation/09-wip/src/Action/Hello.php create mode 100644 implementation/09-wip/src/Action/Other.php create mode 100644 implementation/09-wip/src/Bootstrap.php create mode 100644 implementation/09-wip/src/Exception/InternalServerError.php create mode 100644 implementation/09-wip/src/Exception/MethodNotAllowed.php create mode 100644 implementation/09-wip/src/Exception/NotFound.php create mode 100644 implementation/09-wip/src/Service/Time/Clock.php create mode 100644 implementation/09-wip/src/Service/Time/SystemClock.php diff --git a/implementation/03/composer.json b/implementation/03/composer.json new file mode 100644 index 0000000..a35232a --- /dev/null +++ b/implementation/03/composer.json @@ -0,0 +1,18 @@ +{ + "name": "lubiana/no-framework", + "autoload": { + "psr-4": { + "Lubiana\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "example", + "email": "test@example.com" + } + ], + "require": { + "php": ">=8.1", + "filp/whoops": "^2.14" + } +} diff --git a/implementation/03/public/favicon.ico b/implementation/03/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/03/public/index.php b/implementation/03/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/03/public/index.php @@ -0,0 +1,3 @@ +pushHandler(new PrettyPageHandler()); +} else { + $whoops->pushHandler(function (\Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +echo 'Hello World!'; diff --git a/implementation/04/composer.json b/implementation/04/composer.json new file mode 100644 index 0000000..325bc25 --- /dev/null +++ b/implementation/04/composer.json @@ -0,0 +1,45 @@ +{ + "name": "lubiana/no-framework", + "autoload": { + "psr-4": { + "Lubiana\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "example", + "email": "test@example.com" + } + ], + "require": { + "php": ">=8.1", + "filp/whoops": "^2.14" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/implementation/04/composer.lock b/implementation/04/composer.lock new file mode 100644 index 0000000..7eac750 --- /dev/null +++ b/implementation/04/composer.lock @@ -0,0 +1,872 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "92e03e1fbb1466733bb7150c4a0dec9f", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/04/ecs.php b/implementation/04/ecs.php new file mode 100644 index 0000000..03cbd02 --- /dev/null +++ b/implementation/04/ecs.php @@ -0,0 +1,89 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/implementation/04/phpstan-baseline.neon b/implementation/04/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/implementation/04/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/implementation/04/phpstan.neon b/implementation/04/phpstan.neon new file mode 100644 index 0000000..260e0ff --- /dev/null +++ b/implementation/04/phpstan.neon @@ -0,0 +1,7 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src \ No newline at end of file diff --git a/implementation/04/public/favicon.ico b/implementation/04/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/04/public/index.php b/implementation/04/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/04/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/rector.php', __DIR__ . '/ecs.php']); + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/implementation/04/src/Bootstrap.php b/implementation/04/src/Bootstrap.php new file mode 100644 index 0000000..c5db95f --- /dev/null +++ b/implementation/04/src/Bootstrap.php @@ -0,0 +1,34 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +echo 'Hello World!'; diff --git a/implementation/05/composer.json b/implementation/05/composer.json new file mode 100644 index 0000000..c880259 --- /dev/null +++ b/implementation/05/composer.json @@ -0,0 +1,46 @@ +{ + "name": "lubiana/no-framework", + "autoload": { + "psr-4": { + "Lubiana\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "example", + "email": "test@example.com" + } + ], + "require": { + "php": ">=8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.11" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/implementation/05/composer.lock b/implementation/05/composer.lock new file mode 100644 index 0000000..d03c9d5 --- /dev/null +++ b/implementation/05/composer.lock @@ -0,0 +1,1079 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "bfde95e4f108736027aa3794fc98c8cc", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-05-17T10:57:52+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/05/ecs.php b/implementation/05/ecs.php new file mode 100644 index 0000000..03cbd02 --- /dev/null +++ b/implementation/05/ecs.php @@ -0,0 +1,89 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/implementation/05/phpstan-baseline.neon b/implementation/05/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/implementation/05/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/implementation/05/phpstan.neon b/implementation/05/phpstan.neon new file mode 100644 index 0000000..260e0ff --- /dev/null +++ b/implementation/05/phpstan.neon @@ -0,0 +1,7 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src \ No newline at end of file diff --git a/implementation/05/public/favicon.ico b/implementation/05/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/05/public/index.php b/implementation/05/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/05/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/rector.php', __DIR__ . '/ecs.php']); + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/implementation/05/src/Bootstrap.php b/implementation/05/src/Bootstrap.php new file mode 100644 index 0000000..036a954 --- /dev/null +++ b/implementation/05/src/Bootstrap.php @@ -0,0 +1,63 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +$request = ServerRequestFactory::fromGlobals(); +$response = new Response; +$response->getBody() + ->write('Hello World! '); +$response->getBody() + ->write('The Uri is: ' . $request->getUri()->getPath()); + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/06/composer.json b/implementation/06/composer.json new file mode 100644 index 0000000..61d0e38 --- /dev/null +++ b/implementation/06/composer.json @@ -0,0 +1,47 @@ +{ + "name": "lubiana/no-framework", + "autoload": { + "psr-4": { + "Lubiana\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "example", + "email": "test@example.com" + } + ], + "require": { + "php": ">=8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.11", + "nikic/fast-route": "^1.3" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/implementation/06/composer.lock b/implementation/06/composer.lock new file mode 100644 index 0000000..c737c41 --- /dev/null +++ b/implementation/06/composer.lock @@ -0,0 +1,1129 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "226d7e07ad29221e0052123c575e35df", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-05-17T10:57:52+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/06/config/routes.php b/implementation/06/config/routes.php new file mode 100644 index 0000000..6522454 --- /dev/null +++ b/implementation/06/config/routes.php @@ -0,0 +1,21 @@ +addRoute('GET', '/hello[/{name}]', function (ServerRequestInterface $request) { + $name = $request->getAttribute('name', 'Stranger'); + $response = (new Response)->withStatus(200); + $response->getBody() + ->write('Hello ' . $name . '!'); + return $response; + }); + $r->addRoute('GET', '/other', function (ServerRequestInterface $request) { + $response = (new Response)->withStatus(200); + $response->getBody() + ->write('This works too!'); + return $response; + }); +}; diff --git a/implementation/06/ecs.php b/implementation/06/ecs.php new file mode 100644 index 0000000..6742326 --- /dev/null +++ b/implementation/06/ecs.php @@ -0,0 +1,89 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/implementation/06/phpstan-baseline.neon b/implementation/06/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/implementation/06/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/implementation/06/phpstan.neon b/implementation/06/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/06/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/06/public/favicon.ico b/implementation/06/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/06/public/index.php b/implementation/06/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/06/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/implementation/06/src/Bootstrap.php b/implementation/06/src/Bootstrap.php new file mode 100644 index 0000000..e2944a3 --- /dev/null +++ b/implementation/06/src/Bootstrap.php @@ -0,0 +1,97 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +$request = ServerRequestFactory::fromGlobals(); +$response = new Response; +$response->getBody() + ->write('Hello World! '); +$response->getBody() + ->write('The Uri is: ' . $request->getUri()->getPath()); + + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); + +switch ($routeInfo[0]) { + case Dispatcher::METHOD_NOT_ALLOWED: + $response = (new Response)->withStatus(405); + $response->getBody() + ->write('Method not allowed'); + $response = $response->withStatus(405); + break; + case Dispatcher::FOUND: + $handler = $routeInfo[1]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + /** @var ResponseInterface $response */ + $response = call_user_func($handler, $request); + break; + case Dispatcher::NOT_FOUND: + default: + $response = (new Response)->withStatus(404); + $response->getBody() + ->write('Not Found!'); + break; +} + + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/07/composer.json b/implementation/07/composer.json new file mode 100644 index 0000000..4f79b97 --- /dev/null +++ b/implementation/07/composer.json @@ -0,0 +1,48 @@ +{ + "name": "lubiana/no-framework", + "autoload": { + "psr-4": { + "Lubiana\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "example", + "email": "test@example.com" + } + ], + "require": { + "php": ">=8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.11", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/implementation/07/composer.lock b/implementation/07/composer.lock new file mode 100644 index 0000000..a9f5b96 --- /dev/null +++ b/implementation/07/composer.lock @@ -0,0 +1,1186 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "5f281d245eeab688c1904bf024aeff4f", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-05-17T10:57:52+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/07/config/routes.php b/implementation/07/config/routes.php new file mode 100644 index 0000000..c9b632d --- /dev/null +++ b/implementation/07/config/routes.php @@ -0,0 +1,10 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/other', Other::class); +}; diff --git a/implementation/07/ecs.php b/implementation/07/ecs.php new file mode 100644 index 0000000..6742326 --- /dev/null +++ b/implementation/07/ecs.php @@ -0,0 +1,89 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/implementation/07/phpstan-baseline.neon b/implementation/07/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/implementation/07/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/implementation/07/phpstan.neon b/implementation/07/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/07/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/07/public/favicon.ico b/implementation/07/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/07/public/index.php b/implementation/07/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/07/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/implementation/07/src/Action/Hello.php b/implementation/07/src/Action/Hello.php new file mode 100644 index 0000000..3deb900 --- /dev/null +++ b/implementation/07/src/Action/Hello.php @@ -0,0 +1,20 @@ +getAttribute('name', 'Stranger'); + $response = (new Response)->withStatus(200); + $response->getBody() + ->write('Hello ' . $name . '!'); + return $response; + } +} diff --git a/implementation/07/src/Action/Other.php b/implementation/07/src/Action/Other.php new file mode 100644 index 0000000..5481073 --- /dev/null +++ b/implementation/07/src/Action/Other.php @@ -0,0 +1,19 @@ +withStatus(200); + $response->getBody() + ->write('This works too!'); + return $response; + } +} diff --git a/implementation/07/src/Bootstrap.php b/implementation/07/src/Bootstrap.php new file mode 100644 index 0000000..6317c47 --- /dev/null +++ b/implementation/07/src/Bootstrap.php @@ -0,0 +1,101 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +$request = ServerRequestFactory::fromGlobals(); + + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + $className = $routeInfo[1]; + $handler = new $className; + assert($handler instanceof RequestHandlerInterface); + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody() + ->write('Method not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody() + ->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} + + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/07/src/Exception/InternalServerError.php b/implementation/07/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/07/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +=8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.11", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/implementation/08/composer.lock b/implementation/08/composer.lock new file mode 100644 index 0000000..a9f5b96 --- /dev/null +++ b/implementation/08/composer.lock @@ -0,0 +1,1186 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "5f281d245eeab688c1904bf024aeff4f", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-05-17T10:57:52+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/08/config/routes.php b/implementation/08/config/routes.php new file mode 100644 index 0000000..c9b632d --- /dev/null +++ b/implementation/08/config/routes.php @@ -0,0 +1,10 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/other', Other::class); +}; diff --git a/implementation/08/ecs.php b/implementation/08/ecs.php new file mode 100644 index 0000000..6742326 --- /dev/null +++ b/implementation/08/ecs.php @@ -0,0 +1,89 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/implementation/08/phpstan-baseline.neon b/implementation/08/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/implementation/08/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/implementation/08/phpstan.neon b/implementation/08/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/08/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/08/public/favicon.ico b/implementation/08/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/08/public/index.php b/implementation/08/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/08/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/implementation/08/src/Action/Hello.php b/implementation/08/src/Action/Hello.php new file mode 100644 index 0000000..696421b --- /dev/null +++ b/implementation/08/src/Action/Hello.php @@ -0,0 +1,25 @@ +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $body->write('Hello ' . $name . '!'); + + return $this->response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/08/src/Action/Other.php b/implementation/08/src/Action/Other.php new file mode 100644 index 0000000..c42c74b --- /dev/null +++ b/implementation/08/src/Action/Other.php @@ -0,0 +1,24 @@ +response->getBody(); + + $body->write('This works too!'); + + return $this->response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/08/src/Bootstrap.php b/implementation/08/src/Bootstrap.php new file mode 100644 index 0000000..98a3952 --- /dev/null +++ b/implementation/08/src/Bootstrap.php @@ -0,0 +1,101 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +$request = ServerRequestFactory::fromGlobals(); +$response = new Response; + + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + $className = $routeInfo[1]; + $handler = new $className($response); + assert($handler instanceof RequestHandlerInterface); + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody() + ->write('Method not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody() + ->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/08/src/Exception/InternalServerError.php b/implementation/08/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/08/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +=8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.11", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/implementation/09-wip/composer.lock b/implementation/09-wip/composer.lock new file mode 100644 index 0000000..a9f5b96 --- /dev/null +++ b/implementation/09-wip/composer.lock @@ -0,0 +1,1186 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "5f281d245eeab688c1904bf024aeff4f", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-05-17T10:57:52+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/09-wip/config/container.php b/implementation/09-wip/config/container.php new file mode 100644 index 0000000..87eb322 --- /dev/null +++ b/implementation/09-wip/config/container.php @@ -0,0 +1,9 @@ + fn () => new \Lubian\NoFramework\Action\Hello($response, $clock), + \Lubian\NoFramework\Action\Other::class => fn () => new \Lubian\NoFramework\Action\Other($response), +]; diff --git a/implementation/09-wip/config/routes.php b/implementation/09-wip/config/routes.php new file mode 100644 index 0000000..c9b632d --- /dev/null +++ b/implementation/09-wip/config/routes.php @@ -0,0 +1,10 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/other', Other::class); +}; diff --git a/implementation/09-wip/ecs.php b/implementation/09-wip/ecs.php new file mode 100644 index 0000000..6742326 --- /dev/null +++ b/implementation/09-wip/ecs.php @@ -0,0 +1,89 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/implementation/09-wip/phpstan-baseline.neon b/implementation/09-wip/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/implementation/09-wip/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/implementation/09-wip/phpstan.neon b/implementation/09-wip/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/09-wip/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/09-wip/public/favicon.ico b/implementation/09-wip/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/09-wip/public/index.php b/implementation/09-wip/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/09-wip/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/implementation/09-wip/src/Action/Hello.php b/implementation/09-wip/src/Action/Hello.php new file mode 100644 index 0000000..6b4d24a --- /dev/null +++ b/implementation/09-wip/src/Action/Hello.php @@ -0,0 +1,29 @@ +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $time = $this->clock->now()->format('H:i:s'); + + $body->write('Hello ' . $name . '!
'); + $body->write('The Time is: ' . $time); + + return $this->response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/09-wip/src/Action/Other.php b/implementation/09-wip/src/Action/Other.php new file mode 100644 index 0000000..c42c74b --- /dev/null +++ b/implementation/09-wip/src/Action/Other.php @@ -0,0 +1,24 @@ +response->getBody(); + + $body->write('This works too!'); + + return $this->response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/09-wip/src/Bootstrap.php b/implementation/09-wip/src/Bootstrap.php new file mode 100644 index 0000000..98a3952 --- /dev/null +++ b/implementation/09-wip/src/Bootstrap.php @@ -0,0 +1,101 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +$request = ServerRequestFactory::fromGlobals(); +$response = new Response; + + +$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; +$dispatcher = simpleDispatcher($routeDefinitionCallback); + +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + $className = $routeInfo[1]; + $handler = new $className($response); + assert($handler instanceof RequestHandlerInterface); + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody() + ->write('Method not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody() + ->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/09-wip/src/Exception/InternalServerError.php b/implementation/09-wip/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/09-wip/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ + Date: Fri, 20 May 2022 09:28:47 +0200 Subject: [PATCH 297/314] wip: rewrite di chapter --- 09-dependency-injector.md | 10 + app/composer.json | 49 + app/composer.lock | 1239 +++++++++++++++++++++ app/config/container.php | 37 + app/config/routes.php | 10 + app/ecs.php | 89 ++ app/phpstan-baseline.neon | 6 + app/phpstan.neon | 8 + app/public/index.php | 3 + app/rector.php | 12 + app/src/Action/Hello.php | 29 + app/src/Action/Other.php | 24 + app/src/Bootstrap.php | 104 ++ app/src/Exception/InternalServerError.php | 9 + app/src/Exception/MethodNotAllowed.php | 9 + app/src/Exception/NotFound.php | 9 + app/src/Service/Time/Clock.php | 8 + app/src/Service/Time/SystemClock.php | 12 + 18 files changed, 1667 insertions(+) create mode 100644 app/composer.json create mode 100644 app/composer.lock create mode 100644 app/config/container.php create mode 100644 app/config/routes.php create mode 100644 app/ecs.php create mode 100644 app/phpstan-baseline.neon create mode 100644 app/phpstan.neon create mode 100644 app/public/index.php create mode 100644 app/rector.php create mode 100644 app/src/Action/Hello.php create mode 100644 app/src/Action/Other.php create mode 100644 app/src/Bootstrap.php create mode 100644 app/src/Exception/InternalServerError.php create mode 100644 app/src/Exception/MethodNotAllowed.php create mode 100644 app/src/Exception/NotFound.php create mode 100644 app/src/Service/Time/Clock.php create mode 100644 app/src/Service/Time/SystemClock.php diff --git a/09-dependency-injector.md b/09-dependency-injector.md index cfbeb7a..67d79e3 100644 --- a/09-dependency-injector.md +++ b/09-dependency-injector.md @@ -2,6 +2,16 @@ ### Dependency Injector +In the last chapter we rewrote our Actions to require the response-objet as a constructor parameter, and provided it +in the dispatcher section of our `Bootstrap.php`. As we only have one dependency this works really fine, but if we have +different classes with different dependencies our bootstrap file gets complicated quite quickly. Lets look at an example +to explain the problem and work on a solution. + +#### Adding a clock service + +Lets assume that we want to show the current time in our Hello action. We could easily just call use one of the many +ways to get the current time directly in the handle-method, but maybe we want to make that configurable and + A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when the class is instantiated. diff --git a/app/composer.json b/app/composer.json new file mode 100644 index 0000000..980b03b --- /dev/null +++ b/app/composer.json @@ -0,0 +1,49 @@ +{ + "name": "lubian/no-framework", + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "example", + "email": "test@example.com" + } + ], + "require": { + "php": ">=8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.11", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "psr/container": "^2.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/app/composer.lock b/app/composer.lock new file mode 100644 index 0000000..18e9e6b --- /dev/null +++ b/app/composer.lock @@ -0,0 +1,1239 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "1ccaabdd7944ba2f12098b7b2f1c91c2", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-05-17T10:57:52+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/app/config/container.php b/app/config/container.php new file mode 100644 index 0000000..55766cb --- /dev/null +++ b/app/config/container.php @@ -0,0 +1,37 @@ +services = [ + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), + \Psr\Http\Message\ResponseInterface::class => fn () => new \Laminas\Diactoros\Response(), + \FastRoute\Dispatcher::class => fn () => \FastRoute\simpleDispatcher(require __DIR__ . '/routes.php'), + \Lubian\NoFramework\Service\Time\Clock::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClock(), + \Lubian\NoFramework\Action\Hello::class => fn () => new \Lubian\NoFramework\Action\Hello( + $this->get(\Psr\Http\Message\ResponseInterface::class), + $this->get(\Lubian\NoFramework\Service\Time\Clock::class) + ), + \Lubian\NoFramework\Action\Other::class => fn () => new \Lubian\NoFramework\Action\Other( + $this->get(\Psr\Http\Message\ResponseInterface::class) + ), + ]; + } + + public function get(string $id) + { + if (! $this->has($id)) { + throw new class () extends \Exception implements \Psr\Container\NotFoundExceptionInterface { + }; + } + return $this->services[$id](); + } + + public function has(string $id): bool + { + return array_key_exists($id, $this->services); + } +}; diff --git a/app/config/routes.php b/app/config/routes.php new file mode 100644 index 0000000..c9b632d --- /dev/null +++ b/app/config/routes.php @@ -0,0 +1,10 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/other', Other::class); +}; diff --git a/app/ecs.php b/app/ecs.php new file mode 100644 index 0000000..6742326 --- /dev/null +++ b/app/ecs.php @@ -0,0 +1,89 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/app/phpstan-baseline.neon b/app/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/app/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/app/phpstan.neon b/app/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/app/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/app/public/index.php b/app/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/app/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/app/src/Action/Hello.php b/app/src/Action/Hello.php new file mode 100644 index 0000000..6b4d24a --- /dev/null +++ b/app/src/Action/Hello.php @@ -0,0 +1,29 @@ +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $time = $this->clock->now()->format('H:i:s'); + + $body->write('Hello ' . $name . '!
'); + $body->write('The Time is: ' . $time); + + return $this->response->withBody($body) + ->withStatus(200); + } +} diff --git a/app/src/Action/Other.php b/app/src/Action/Other.php new file mode 100644 index 0000000..c42c74b --- /dev/null +++ b/app/src/Action/Other.php @@ -0,0 +1,24 @@ +response->getBody(); + + $body->write('This works too!'); + + return $this->response->withBody($body) + ->withStatus(200); + } +} diff --git a/app/src/Bootstrap.php b/app/src/Bootstrap.php new file mode 100644 index 0000000..023c8c0 --- /dev/null +++ b/app/src/Bootstrap.php @@ -0,0 +1,104 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +$container = require __DIR__ . '/../config/container.php'; +assert($container instanceof ContainerInterface); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$dispatcher = $container->get(Dispatcher::class); +assert($dispatcher instanceof Dispatcher); + + +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + $className = $routeInfo[1]; + $handler = $container->get($className); + assert($handler instanceof RequestHandlerInterface); + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $response = $handler->handle($request); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody() + ->write('Method not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody() + ->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/app/src/Exception/InternalServerError.php b/app/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/app/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ + Date: Sat, 21 May 2022 00:21:15 +0200 Subject: [PATCH 298/314] explain implementation of ad-hoc depencency container --- 09-dependency-injector.md | 193 +++++++++++++++++++++++++++++++++++++- app/src/Action/Hello.php | 6 +- app/src/Bootstrap.php | 2 +- 3 files changed, 197 insertions(+), 4 deletions(-) diff --git a/09-dependency-injector.md b/09-dependency-injector.md index 67d79e3..8b0b5eb 100644 --- a/09-dependency-injector.md +++ b/09-dependency-injector.md @@ -10,12 +10,201 @@ to explain the problem and work on a solution. #### Adding a clock service Lets assume that we want to show the current time in our Hello action. We could easily just call use one of the many -ways to get the current time directly in the handle-method, but maybe we want to make that configurable and +ways to get the current time directly in the handle-method, but lets create a separate class and interface for that so +we can later configure and switch our implementation. + +We need a new 'Service\Time' namespace, so lets first create the folder in our 'src' directory 'src/Service/Time'. +There we place a Clock.php interface and a SystemClock.php implementation: + + +The Clock.php interface: +```php +getAttribute('name', 'Stranger'); + $body = $this->response->getBody(); + + $time = $this->clock->now()->format('H:i:s'); + + $body->write('Hello ' . $name . '!
'); + $body->write('The Time is: ' . $time); + + return $this->response->withBody($body) + ->withStatus(200); + } +} +``` + +But if we try to access the corresponding route in the webbrowser we get an error: +> Too few arguments to function Lubian\NoFramework\Action\Hello::__construct(), 1 passed in /home/lubiana/PhpstormProjects/no-framework/app/src/Bootstrap.php on line 62 and exactly 2 expected + +Our current problem is, that we have two Actions defined, which both have different constructor requirements. That means, +that we need to have some code in our Application, that creates our Action Objects and takes care of injection all the +needed dependencies. + +This code is called a Dependency Injector. If you want you can read [this](https://afilina.com/learn/design/dependency-injection) +great blogpost about that topic, which I highly recommend. + +Lets build our own Dependency Injector to make our application work again. + +As a starting point we are going to take a look at the [Container Interface])(https://www.php-fig.org/psr/psr-11/) that +is widely adopted in the PHP-World. + +#### Building a dependency container + +**Short Disclaimer:** *Although it would be fun to write our own great implementation of this interface with everything that +is needed for modern php development I will take a shortcut here and implement very reduced version to show you the +basic concept.* + +The `Pst\Container\ContainerIterface` defines two methods: + +* has($id): bool + returns true if the container can provide a value for a given ID +* get($id): mixed + returns some kind of value that is registered in the container for the given ID + +I mostly define an Interface or a fully qualified classname as an ID. That way I can query the container for +the Clock interface or an Action class and get an object of that class or an object implementing the given Interface. + +For the sake of this tutorial we will put a new file in our config folder that returns an anonymous class implementing +the containerinterface. + +In this class we will configure all services required for our application and make them accessible via the get($id) +method. + +`config/container.php`: +```php +services = [ + \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), + \Psr\Http\Message\ResponseInterface::class => fn () => new \Laminas\Diactoros\Response(), + \FastRoute\Dispatcher::class => fn () => \FastRoute\simpleDispatcher(require __DIR__ . '/routes.php'), + \Lubian\NoFramework\Service\Time\Clock::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClock(), + \Lubian\NoFramework\Action\Hello::class => fn () => new \Lubian\NoFramework\Action\Hello( + $this->get(\Psr\Http\Message\ResponseInterface::class), + $this->get(\Lubian\NoFramework\Service\Time\Clock::class) + ), + \Lubian\NoFramework\Action\Other::class => fn () => new \Lubian\NoFramework\Action\Other( + $this->get(\Psr\Http\Message\ResponseInterface::class) + ), + ]; + } + + public function get(string $id) + { + if (! $this->has($id)) { + throw new class () extends \Exception implements \Psr\Container\NotFoundExceptionInterface { + }; + } + return $this->services[$id](); + } + + public function has(string $id): bool + { + return array_key_exists($id, $this->services); + } +}; +``` + +Here I have declared a services array, that has a class- or interfacename as the keys, and the values are short +closures that return an Object of the defined class or interface. The `has` method simply checks if the given id is +defined in our services array, and the `get` method calls the closure defined in the array for the given id key and then +returns the result of that closure. + +To use the container we need to update our Bootstrap.php. Firstly we need to get an instance of our container, and then +use that to create our Request-Object as well as the Dispatcher. So remove the manual instantion of those objects and +replace that with the following code: + +```php +$container = require __DIR__ . '/../config/container.php'; +assert($container instanceof \Psr\Container\ContainerInterface); + +$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); +assert($request instanceof \Psr\Http\Message\ServerRequestInterface); + +$dispatcher = $container->get(FastRoute\Dispatcher::class); +assert($dispatcher instanceof \FastRoute\Dispatcher); +``` + +In the Dispatcher switch block we manually build our handler object with this two lines: + + +```php +$handler = new $className($response); +assert($handler instanceof RequestHandlerInterface); +``` + +Instead of manually creating the Handler-Instance we are going to kindly ask the Container to build it for us: + +```php +$handler = $container->get($className); +assert($handler instanceof RequestHandlerInterface); +``` + +If you now open the `/hello` route in your browser everything should work again! + +#### Using Autowiring + + A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when the class is instantiated. -Again the psr has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work +Again the FIG has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation). diff --git a/app/src/Action/Hello.php b/app/src/Action/Hello.php index 6b4d24a..d107411 100644 --- a/app/src/Action/Hello.php +++ b/app/src/Action/Hello.php @@ -2,6 +2,7 @@ namespace Lubian\NoFramework\Action; + use Lubian\NoFramework\Service\Time\Clock; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -9,7 +10,10 @@ use Psr\Http\Server\RequestHandlerInterface; final class Hello implements RequestHandlerInterface { - public function __construct(private readonly ResponseInterface $response, private readonly Clock $clock) + public function __construct( + private readonly ResponseInterface $response, + private readonly Clock $clock + ) { } diff --git a/app/src/Bootstrap.php b/app/src/Bootstrap.php index 023c8c0..4fe4a67 100644 --- a/app/src/Bootstrap.php +++ b/app/src/Bootstrap.php @@ -59,7 +59,7 @@ try { switch ($routeInfo[0]) { case Dispatcher::FOUND: $className = $routeInfo[1]; - $handler = $container->get($className); + $handler = new $className($response); assert($handler instanceof RequestHandlerInterface); foreach ($routeInfo[2] as $attributeName => $attributeValue) { $request = $request->withAttribute($attributeName, $attributeValue); From dfb3f0aee062516e519923ae43400f4c17ff89a0 Mon Sep 17 00:00:00 2001 From: lubiana Date: Sat, 21 May 2022 00:53:33 +0200 Subject: [PATCH 299/314] explain implementation of ad-hoc depencency container --- 09-dependency-injector.md | 217 +++-------------------- app/composer.json | 3 +- app/composer.lock | 255 +++++++++++++++++++++++++-- app/config/container.php | 48 ++--- app/src/Action/Hello.php | 7 +- app/src/Bootstrap.php | 2 +- app/src/Service/Time/Clock.php | 6 +- app/src/Service/Time/SystemClock.php | 9 +- 8 files changed, 297 insertions(+), 250 deletions(-) diff --git a/09-dependency-injector.md b/09-dependency-injector.md index 8b0b5eb..060a09f 100644 --- a/09-dependency-injector.md +++ b/09-dependency-injector.md @@ -119,6 +119,10 @@ the containerinterface. In this class we will configure all services required for our application and make them accessible via the get($id) method. +p +Before we can implement the interface we need to install its definition with composer `composer require "psr/container:^1.0"`. +now we can create a file with a Class that implements that interface. + `config/container.php`: ```php addDefinitions([ - \Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class), \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), + \Psr\Http\Message\ResponseInterface::class => fn () => new \Laminas\Diactoros\Response(), + \FastRoute\Dispatcher::class => fn () => \FastRoute\simpleDispatcher(require __DIR__ . '/routes.php'), + \Lubian\NoFramework\Service\Time\Clock::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClock(), ]); return $builder->build(); ``` -In this file we create a containerbuilder, add some definitions to it and return the container. -As the container supports autowiring we only need to define services where we want to use a specific implementation of -an interface. +As the PHP-DI container that is return by the `$builder->build()` method implements the same container interface as our +previously used ad-hoc container we won't need to update the our Bootstrap file and everything still works. -In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to -tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second -exampler I choose to write a small factory closure that wraps the Laminas Requestfactory. - -Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types), -as we will use that extensively. - -Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally -to create our Handler-Object - -```php -$container = require __DIR__ . '/../config/dependencies.php'; -assert($container instanceof \Psr\Container\ContainerInterface); - -$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class); -assert($request instanceof \Psr\Http\Message\ServerRequestInterface); -``` - -The other part that has to be changed is the dispatching of the route. Before you had the following code: - -```php -$className = $routeInfo[1]; -$handler = new $className($response); -assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface) -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Change that to the following: - -```php -/** @var RequestHandlerInterface $handler */ -$className = $routeInfo[1]; -$handler = $container->get($className); -assert($handler instanceof RequestHandlerInterface); -foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); -} -$response = $handler->handle($request); -``` - -Make sure to use the container fetch the response object in the catch blocks as well: - -```php -} catch (MethodNotAllowed) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(405); - $response->getBody()->write('Not Allowed'); -} catch (NotFound) { - $response = $container->get(ResponseInterface::class); - assert($response instanceof ResponseInterface); - $response = $response->withStatus(404); - $response->getBody()->write('Not Found'); -} -``` - -Now all your controller constructor dependencies will be automatically resolved with PHP-DI. - -We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons -in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call -`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we -want to work with a different date in a test. - -Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation -that creates us a DateTimeImmutable object with the current date and time. - -src/Service/Time/Now.php: -```php -namespace Lubian\NoFramework\Service\Time; - -interface Now -{ - public function __invoke(): \DateTimeImmutable; -} -``` -src/Service/Time/SystemClockNow.php: -```php -namespace Lubian\NoFramework\Service\Time; - -final class SystemClockNow implements Now -{ - - public function __invoke(): \DateTimeImmutable - { - return new \DateTimeImmutable; - } -} -``` -If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and -update the handle-method to use the new class property: - -```php -getAttribute('name', 'Stranger'); - $nowAsString = ($this->now)()->format('H:i:s'); - $body = $this->response->getBody(); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowAsString); - - return $this->response - ->withBody($body) - ->withStatus(200); - } -} -``` - -If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI -automatically figures out what classes are requested in the constructor and tries to create the objects needed. - -But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred -S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface: - -```php - public function __construct( - private ResponseInterface $response, - private Now $now, - ) -``` - -When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation -should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file: - -```php -\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(), -``` - -we could also use the PHP-DI create method to delegate the object creation to the container implementation: -```php -\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class), -``` - -this way the container can try to resolve any dependencies that the class might have internally, but prefer the other -method because we are not depending on this specific dependency injection implementation. - -Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are -requesting the Hello action. - -If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As -we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way -we wont get any warnings for this particular issue, but for any other issues we add to our code. - -Update the phpstan.neon file to include a "baseline" file: - -``` -includes: - - phpstan-baseline.neon - -parameters: - level: 9 - paths: - - src -``` - -if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and -ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just -'baseline' [<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file diff --git a/app/composer.json b/app/composer.json index 980b03b..335b122 100644 --- a/app/composer.json +++ b/app/composer.json @@ -17,7 +17,8 @@ "laminas/laminas-diactoros": "^2.11", "nikic/fast-route": "^1.3", "psr/http-server-handler": "^1.0", - "psr/container": "^2.0" + "psr/container": "^1.0", + "php-di/php-di": "^6.4" }, "require-dev": { "phpstan/phpstan": "^1.6", diff --git a/app/composer.lock b/app/composer.lock index 18e9e6b..386eb40 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1ccaabdd7944ba2f12098b7b2f1c91c2", + "content-hash": "0b6833b8fa6869bd212824769648e667", "packages": [ { "name": "filp/whoops", @@ -176,6 +176,65 @@ ], "time": "2022-05-17T10:57:52+00:00" }, + { + "name": "laravel/serializable-closure", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "pestphp/pest": "^1.18", + "phpstan/phpstan": "^0.12.98", + "symfony/var-dumper": "^5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2022-05-16T17:09:47+00:00" + }, { "name": "nikic/fast-route", "version": "v1.3.0", @@ -227,28 +286,196 @@ "time": "2018-02-13T20:26:39+00:00" }, { - "name": "psr/container", - "version": "2.0.2", + "name": "php-di/invoker", + "version": "2.3.3", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.4.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "shasum": "" + }, + "require": { + "laravel/serializable-closure": "^1.0", + "php": ">=7.4.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.10", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.11.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^9.5" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.4.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2022-04-09T16:46:38+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", "shasum": "" }, "require": { "php": ">=7.4.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, "autoload": { "psr-4": { "Psr\\Container\\": "src/" @@ -275,9 +502,9 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" + "source": "https://github.com/php-fig/container/tree/1.1.2" }, - "time": "2021-11-05T16:47:00+00:00" + "time": "2021-11-05T16:50:12+00:00" }, { "name": "psr/http-factory", diff --git a/app/config/container.php b/app/config/container.php index 55766cb..ceb20dc 100644 --- a/app/config/container.php +++ b/app/config/container.php @@ -1,37 +1,23 @@ services = [ - \Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(), - \Psr\Http\Message\ResponseInterface::class => fn () => new \Laminas\Diactoros\Response(), - \FastRoute\Dispatcher::class => fn () => \FastRoute\simpleDispatcher(require __DIR__ . '/routes.php'), - \Lubian\NoFramework\Service\Time\Clock::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClock(), - \Lubian\NoFramework\Action\Hello::class => fn () => new \Lubian\NoFramework\Action\Hello( - $this->get(\Psr\Http\Message\ResponseInterface::class), - $this->get(\Lubian\NoFramework\Service\Time\Clock::class) - ), - \Lubian\NoFramework\Action\Other::class => fn () => new \Lubian\NoFramework\Action\Other( - $this->get(\Psr\Http\Message\ResponseInterface::class) - ), - ]; - } +$builder = new ContainerBuilder; - public function get(string $id) - { - if (! $this->has($id)) { - throw new class () extends \Exception implements \Psr\Container\NotFoundExceptionInterface { - }; - } - return $this->services[$id](); - } +$builder->addDefinitions([ + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + ResponseInterface::class => fn () => new Response, + Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + Clock::class => fn () => new SystemClock, +]); - public function has(string $id): bool - { - return array_key_exists($id, $this->services); - } -}; +return $builder->build(); diff --git a/app/src/Action/Hello.php b/app/src/Action/Hello.php index d107411..ec2e00c 100644 --- a/app/src/Action/Hello.php +++ b/app/src/Action/Hello.php @@ -2,7 +2,6 @@ namespace Lubian\NoFramework\Action; - use Lubian\NoFramework\Service\Time\Clock; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -13,8 +12,7 @@ final class Hello implements RequestHandlerInterface public function __construct( private readonly ResponseInterface $response, private readonly Clock $clock - ) - { + ) { } public function handle(ServerRequestInterface $request): ResponseInterface @@ -22,7 +20,8 @@ final class Hello implements RequestHandlerInterface $name = $request->getAttribute('name', 'Stranger'); $body = $this->response->getBody(); - $time = $this->clock->now()->format('H:i:s'); + $time = $this->clock->now() + ->format('H:i:s'); $body->write('Hello ' . $name . '!
'); $body->write('The Time is: ' . $time); diff --git a/app/src/Bootstrap.php b/app/src/Bootstrap.php index 4fe4a67..023c8c0 100644 --- a/app/src/Bootstrap.php +++ b/app/src/Bootstrap.php @@ -59,7 +59,7 @@ try { switch ($routeInfo[0]) { case Dispatcher::FOUND: $className = $routeInfo[1]; - $handler = new $className($response); + $handler = $container->get($className); assert($handler instanceof RequestHandlerInterface); foreach ($routeInfo[2] as $attributeName => $attributeValue) { $request = $request->withAttribute($attributeName, $attributeValue); diff --git a/app/src/Service/Time/Clock.php b/app/src/Service/Time/Clock.php index 50897dc..204ccb4 100644 --- a/app/src/Service/Time/Clock.php +++ b/app/src/Service/Time/Clock.php @@ -2,7 +2,9 @@ namespace Lubian\NoFramework\Service\Time; +use DateTimeImmutable; + interface Clock { - public function now(): \DateTimeImmutable; -} \ No newline at end of file + public function now(): DateTimeImmutable; +} diff --git a/app/src/Service/Time/SystemClock.php b/app/src/Service/Time/SystemClock.php index f0d0d0a..705d81f 100644 --- a/app/src/Service/Time/SystemClock.php +++ b/app/src/Service/Time/SystemClock.php @@ -2,11 +2,12 @@ namespace Lubian\NoFramework\Service\Time; +use DateTimeImmutable; + final class SystemClock implements Clock { - public function now(): \DateTimeImmutable + public function now(): DateTimeImmutable { - return new \DateTimeImmutable(); + return new DateTimeImmutable; } - -} \ No newline at end of file +} From 64ca2ece2eaab36783434aba01defe7f1c2ce711 Mon Sep 17 00:00:00 2001 From: lubiana Date: Sat, 21 May 2022 00:54:36 +0200 Subject: [PATCH 300/314] rename implementation 09-wip directory --- implementation/{09-wip => 09}/composer.json | 0 implementation/{09-wip => 09}/composer.lock | 0 implementation/{09-wip => 09}/config/container.php | 0 implementation/{09-wip => 09}/config/routes.php | 0 implementation/{09-wip => 09}/ecs.php | 0 implementation/{09-wip => 09}/phpstan-baseline.neon | 0 implementation/{09-wip => 09}/phpstan.neon | 0 implementation/{09-wip => 09}/public/favicon.ico | Bin implementation/{09-wip => 09}/public/index.php | 0 implementation/{09-wip => 09}/rector.php | 0 implementation/{09-wip => 09}/src/Action/Hello.php | 0 implementation/{09-wip => 09}/src/Action/Other.php | 0 implementation/{09-wip => 09}/src/Bootstrap.php | 0 .../src/Exception/InternalServerError.php | 0 .../src/Exception/MethodNotAllowed.php | 0 .../{09-wip => 09}/src/Exception/NotFound.php | 0 .../{09-wip => 09}/src/Service/Time/Clock.php | 0 .../{09-wip => 09}/src/Service/Time/SystemClock.php | 0 18 files changed, 0 insertions(+), 0 deletions(-) rename implementation/{09-wip => 09}/composer.json (100%) rename implementation/{09-wip => 09}/composer.lock (100%) rename implementation/{09-wip => 09}/config/container.php (100%) rename implementation/{09-wip => 09}/config/routes.php (100%) rename implementation/{09-wip => 09}/ecs.php (100%) rename implementation/{09-wip => 09}/phpstan-baseline.neon (100%) rename implementation/{09-wip => 09}/phpstan.neon (100%) rename implementation/{09-wip => 09}/public/favicon.ico (100%) rename implementation/{09-wip => 09}/public/index.php (100%) rename implementation/{09-wip => 09}/rector.php (100%) rename implementation/{09-wip => 09}/src/Action/Hello.php (100%) rename implementation/{09-wip => 09}/src/Action/Other.php (100%) rename implementation/{09-wip => 09}/src/Bootstrap.php (100%) rename implementation/{09-wip => 09}/src/Exception/InternalServerError.php (100%) rename implementation/{09-wip => 09}/src/Exception/MethodNotAllowed.php (100%) rename implementation/{09-wip => 09}/src/Exception/NotFound.php (100%) rename implementation/{09-wip => 09}/src/Service/Time/Clock.php (100%) rename implementation/{09-wip => 09}/src/Service/Time/SystemClock.php (100%) diff --git a/implementation/09-wip/composer.json b/implementation/09/composer.json similarity index 100% rename from implementation/09-wip/composer.json rename to implementation/09/composer.json diff --git a/implementation/09-wip/composer.lock b/implementation/09/composer.lock similarity index 100% rename from implementation/09-wip/composer.lock rename to implementation/09/composer.lock diff --git a/implementation/09-wip/config/container.php b/implementation/09/config/container.php similarity index 100% rename from implementation/09-wip/config/container.php rename to implementation/09/config/container.php diff --git a/implementation/09-wip/config/routes.php b/implementation/09/config/routes.php similarity index 100% rename from implementation/09-wip/config/routes.php rename to implementation/09/config/routes.php diff --git a/implementation/09-wip/ecs.php b/implementation/09/ecs.php similarity index 100% rename from implementation/09-wip/ecs.php rename to implementation/09/ecs.php diff --git a/implementation/09-wip/phpstan-baseline.neon b/implementation/09/phpstan-baseline.neon similarity index 100% rename from implementation/09-wip/phpstan-baseline.neon rename to implementation/09/phpstan-baseline.neon diff --git a/implementation/09-wip/phpstan.neon b/implementation/09/phpstan.neon similarity index 100% rename from implementation/09-wip/phpstan.neon rename to implementation/09/phpstan.neon diff --git a/implementation/09-wip/public/favicon.ico b/implementation/09/public/favicon.ico similarity index 100% rename from implementation/09-wip/public/favicon.ico rename to implementation/09/public/favicon.ico diff --git a/implementation/09-wip/public/index.php b/implementation/09/public/index.php similarity index 100% rename from implementation/09-wip/public/index.php rename to implementation/09/public/index.php diff --git a/implementation/09-wip/rector.php b/implementation/09/rector.php similarity index 100% rename from implementation/09-wip/rector.php rename to implementation/09/rector.php diff --git a/implementation/09-wip/src/Action/Hello.php b/implementation/09/src/Action/Hello.php similarity index 100% rename from implementation/09-wip/src/Action/Hello.php rename to implementation/09/src/Action/Hello.php diff --git a/implementation/09-wip/src/Action/Other.php b/implementation/09/src/Action/Other.php similarity index 100% rename from implementation/09-wip/src/Action/Other.php rename to implementation/09/src/Action/Other.php diff --git a/implementation/09-wip/src/Bootstrap.php b/implementation/09/src/Bootstrap.php similarity index 100% rename from implementation/09-wip/src/Bootstrap.php rename to implementation/09/src/Bootstrap.php diff --git a/implementation/09-wip/src/Exception/InternalServerError.php b/implementation/09/src/Exception/InternalServerError.php similarity index 100% rename from implementation/09-wip/src/Exception/InternalServerError.php rename to implementation/09/src/Exception/InternalServerError.php diff --git a/implementation/09-wip/src/Exception/MethodNotAllowed.php b/implementation/09/src/Exception/MethodNotAllowed.php similarity index 100% rename from implementation/09-wip/src/Exception/MethodNotAllowed.php rename to implementation/09/src/Exception/MethodNotAllowed.php diff --git a/implementation/09-wip/src/Exception/NotFound.php b/implementation/09/src/Exception/NotFound.php similarity index 100% rename from implementation/09-wip/src/Exception/NotFound.php rename to implementation/09/src/Exception/NotFound.php diff --git a/implementation/09-wip/src/Service/Time/Clock.php b/implementation/09/src/Service/Time/Clock.php similarity index 100% rename from implementation/09-wip/src/Service/Time/Clock.php rename to implementation/09/src/Service/Time/Clock.php diff --git a/implementation/09-wip/src/Service/Time/SystemClock.php b/implementation/09/src/Service/Time/SystemClock.php similarity index 100% rename from implementation/09-wip/src/Service/Time/SystemClock.php rename to implementation/09/src/Service/Time/SystemClock.php From f743663b21a219042bf828688244a63ea0b60957 Mon Sep 17 00:00:00 2001 From: lubiana Date: Sat, 21 May 2022 00:57:02 +0200 Subject: [PATCH 301/314] update implementation of chapter 9 --- implementation/09/composer.json | 4 +- implementation/09/composer.lock | 282 +++++++++++++++++- implementation/09/config/container.php | 26 +- implementation/09/src/Action/Hello.php | 9 +- implementation/09/src/Bootstrap.php | 17 +- implementation/09/src/Service/Time/Clock.php | 6 +- .../09/src/Service/Time/SystemClock.php | 9 +- 7 files changed, 329 insertions(+), 24 deletions(-) diff --git a/implementation/09/composer.json b/implementation/09/composer.json index 74e3551..335b122 100644 --- a/implementation/09/composer.json +++ b/implementation/09/composer.json @@ -16,7 +16,9 @@ "filp/whoops": "^2.14", "laminas/laminas-diactoros": "^2.11", "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0" + "psr/http-server-handler": "^1.0", + "psr/container": "^1.0", + "php-di/php-di": "^6.4" }, "require-dev": { "phpstan/phpstan": "^1.6", diff --git a/implementation/09/composer.lock b/implementation/09/composer.lock index a9f5b96..386eb40 100644 --- a/implementation/09/composer.lock +++ b/implementation/09/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5f281d245eeab688c1904bf024aeff4f", + "content-hash": "0b6833b8fa6869bd212824769648e667", "packages": [ { "name": "filp/whoops", @@ -176,6 +176,65 @@ ], "time": "2022-05-17T10:57:52+00:00" }, + { + "name": "laravel/serializable-closure", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "pestphp/pest": "^1.18", + "phpstan/phpstan": "^0.12.98", + "symfony/var-dumper": "^5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2022-05-16T17:09:47+00:00" + }, { "name": "nikic/fast-route", "version": "v1.3.0", @@ -226,6 +285,227 @@ }, "time": "2018-02-13T20:26:39+00:00" }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.4.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "shasum": "" + }, + "require": { + "laravel/serializable-closure": "^1.0", + "php": ">=7.4.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.10", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.11.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^9.5" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.4.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2022-04-09T16:46:38+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, { "name": "psr/http-factory", "version": "1.0.1", diff --git a/implementation/09/config/container.php b/implementation/09/config/container.php index 87eb322..ceb20dc 100644 --- a/implementation/09/config/container.php +++ b/implementation/09/config/container.php @@ -1,9 +1,23 @@ fn () => new \Lubian\NoFramework\Action\Hello($response, $clock), - \Lubian\NoFramework\Action\Other::class => fn () => new \Lubian\NoFramework\Action\Other($response), -]; +use function FastRoute\simpleDispatcher; + +$builder = new ContainerBuilder; + +$builder->addDefinitions([ + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + ResponseInterface::class => fn () => new Response, + Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + Clock::class => fn () => new SystemClock, +]); + +return $builder->build(); diff --git a/implementation/09/src/Action/Hello.php b/implementation/09/src/Action/Hello.php index 6b4d24a..ec2e00c 100644 --- a/implementation/09/src/Action/Hello.php +++ b/implementation/09/src/Action/Hello.php @@ -9,8 +9,10 @@ use Psr\Http\Server\RequestHandlerInterface; final class Hello implements RequestHandlerInterface { - public function __construct(private readonly ResponseInterface $response, private readonly Clock $clock) - { + public function __construct( + private readonly ResponseInterface $response, + private readonly Clock $clock + ) { } public function handle(ServerRequestInterface $request): ResponseInterface @@ -18,7 +20,8 @@ final class Hello implements RequestHandlerInterface $name = $request->getAttribute('name', 'Stranger'); $body = $this->response->getBody(); - $time = $this->clock->now()->format('H:i:s'); + $time = $this->clock->now() + ->format('H:i:s'); $body->write('Hello ' . $name . '!
'); $body->write('The Time is: ' . $time); diff --git a/implementation/09/src/Bootstrap.php b/implementation/09/src/Bootstrap.php index 98a3952..023c8c0 100644 --- a/implementation/09/src/Bootstrap.php +++ b/implementation/09/src/Bootstrap.php @@ -4,10 +4,11 @@ namespace Lubian\NoFramework; use FastRoute\Dispatcher; use Laminas\Diactoros\Response; -use Laminas\Diactoros\ServerRequestFactory; use Lubian\NoFramework\Exception\InternalServerError; use Lubian\NoFramework\Exception\MethodNotAllowed; use Lubian\NoFramework\Exception\NotFound; +use Psr\Container\ContainerInterface; +use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use Throwable; use Whoops\Handler\PrettyPageHandler; @@ -16,7 +17,6 @@ use Whoops\Run; use function assert; use function error_log; use function error_reporting; -use function FastRoute\simpleDispatcher; use function getenv; use function header; use function sprintf; @@ -43,12 +43,15 @@ if ($environment === 'dev') { $whoops->register(); -$request = ServerRequestFactory::fromGlobals(); -$response = new Response; +$container = require __DIR__ . '/../config/container.php'; +assert($container instanceof ContainerInterface); +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$dispatcher = $container->get(Dispatcher::class); +assert($dispatcher instanceof Dispatcher); -$routeDefinitionCallback = require __DIR__ . '/../config/routes.php'; -$dispatcher = simpleDispatcher($routeDefinitionCallback); $routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); @@ -56,7 +59,7 @@ try { switch ($routeInfo[0]) { case Dispatcher::FOUND: $className = $routeInfo[1]; - $handler = new $className($response); + $handler = $container->get($className); assert($handler instanceof RequestHandlerInterface); foreach ($routeInfo[2] as $attributeName => $attributeValue) { $request = $request->withAttribute($attributeName, $attributeValue); diff --git a/implementation/09/src/Service/Time/Clock.php b/implementation/09/src/Service/Time/Clock.php index 50897dc..204ccb4 100644 --- a/implementation/09/src/Service/Time/Clock.php +++ b/implementation/09/src/Service/Time/Clock.php @@ -2,7 +2,9 @@ namespace Lubian\NoFramework\Service\Time; +use DateTimeImmutable; + interface Clock { - public function now(): \DateTimeImmutable; -} \ No newline at end of file + public function now(): DateTimeImmutable; +} diff --git a/implementation/09/src/Service/Time/SystemClock.php b/implementation/09/src/Service/Time/SystemClock.php index f0d0d0a..705d81f 100644 --- a/implementation/09/src/Service/Time/SystemClock.php +++ b/implementation/09/src/Service/Time/SystemClock.php @@ -2,11 +2,12 @@ namespace Lubian\NoFramework\Service\Time; +use DateTimeImmutable; + final class SystemClock implements Clock { - public function now(): \DateTimeImmutable + public function now(): DateTimeImmutable { - return new \DateTimeImmutable(); + return new DateTimeImmutable; } - -} \ No newline at end of file +} From 9c9df27942682c29ae5639f665f0088c19ddcda6 Mon Sep 17 00:00:00 2001 From: lubiana Date: Sat, 21 May 2022 00:58:26 +0200 Subject: [PATCH 302/314] remove implementation from app directory --- app/composer.json | 50 - app/composer.lock | 1466 --------------------- app/config/container.php | 23 - app/config/routes.php | 10 - app/ecs.php | 89 -- app/phpstan-baseline.neon | 6 - app/phpstan.neon | 8 - app/public/index.php | 3 - app/rector.php | 12 - app/src/Action/Hello.php | 32 - app/src/Action/Other.php | 24 - app/src/Bootstrap.php | 104 -- app/src/Exception/InternalServerError.php | 9 - app/src/Exception/MethodNotAllowed.php | 9 - app/src/Exception/NotFound.php | 9 - app/src/Service/Time/Clock.php | 10 - app/src/Service/Time/SystemClock.php | 13 - 17 files changed, 1877 deletions(-) delete mode 100644 app/composer.json delete mode 100644 app/composer.lock delete mode 100644 app/config/container.php delete mode 100644 app/config/routes.php delete mode 100644 app/ecs.php delete mode 100644 app/phpstan-baseline.neon delete mode 100644 app/phpstan.neon delete mode 100644 app/public/index.php delete mode 100644 app/rector.php delete mode 100644 app/src/Action/Hello.php delete mode 100644 app/src/Action/Other.php delete mode 100644 app/src/Bootstrap.php delete mode 100644 app/src/Exception/InternalServerError.php delete mode 100644 app/src/Exception/MethodNotAllowed.php delete mode 100644 app/src/Exception/NotFound.php delete mode 100644 app/src/Service/Time/Clock.php delete mode 100644 app/src/Service/Time/SystemClock.php diff --git a/app/composer.json b/app/composer.json deleted file mode 100644 index 335b122..0000000 --- a/app/composer.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "lubian/no-framework", - "autoload": { - "psr-4": { - "Lubian\\NoFramework\\": "src/" - } - }, - "authors": [ - { - "name": "example", - "email": "test@example.com" - } - ], - "require": { - "php": ">=8.1", - "filp/whoops": "^2.14", - "laminas/laminas-diactoros": "^2.11", - "nikic/fast-route": "^1.3", - "psr/http-server-handler": "^1.0", - "psr/container": "^1.0", - "php-di/php-di": "^6.4" - }, - "require-dev": { - "phpstan/phpstan": "^1.6", - "symfony/var-dumper": "^6.0", - "slevomat/coding-standard": "^7.2", - "symplify/easy-coding-standard": "^10.2", - "rector/rector": "^0.12.23", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-strict-rules": "^1.2", - "thecodingmachine/phpstan-strict-rules": "^1.0" - }, - "config": { - "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true, - "phpstan/extension-installer": true - } - }, - "scripts": { - "serve": [ - "Composer\\Config::disableProcessTimeout", - "php -S 0.0.0.0:1235 -t public" - ], - "phpstan": "./vendor/bin/phpstan analyze", - "baseline": "./vendor/bin/phpstan analyze --generate-baseline", - "check": "./vendor/bin/ecs", - "fix": "./vendor/bin/ecs --fix", - "rector": "./vendor/bin/rector process" - } -} diff --git a/app/composer.lock b/app/composer.lock deleted file mode 100644 index 386eb40..0000000 --- a/app/composer.lock +++ /dev/null @@ -1,1466 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "0b6833b8fa6869bd212824769648e667", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.11.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", - "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2022-05-17T10:57:52+00:00" - }, - { - "name": "laravel/serializable-closure", - "version": "v1.2.0", - "source": { - "type": "git", - "url": "https://github.com/laravel/serializable-closure.git", - "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", - "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", - "shasum": "" - }, - "require": { - "php": "^7.3|^8.0" - }, - "require-dev": { - "pestphp/pest": "^1.18", - "phpstan/phpstan": "^0.12.98", - "symfony/var-dumper": "^5.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Laravel\\SerializableClosure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - }, - { - "name": "Nuno Maduro", - "email": "nuno@laravel.com" - } - ], - "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", - "keywords": [ - "closure", - "laravel", - "serializable" - ], - "support": { - "issues": "https://github.com/laravel/serializable-closure/issues", - "source": "https://github.com/laravel/serializable-closure" - }, - "time": "2022-05-16T17:09:47+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.4.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/ae0f1b3b03d8b29dff81747063cbfd6276246cc4", - "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4", - "shasum": "" - }, - "require": { - "laravel/serializable-closure": "^1.0", - "php": ">=7.4.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.10", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.11.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^9.5" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.4.0" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2022-04-09T16:46:38+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "1.5.1", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "981cc368a216c988e862a75e526b6076987d1b50" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", - "reference": "981cc368a216c988e862a75e526b6076987d1b50", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.5", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5", - "symfony/process": "^5.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" - }, - "time": "2022-05-05T11:32:40+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.6.8", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", - "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.6.8" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-05-10T06:54:21+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.2.3", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", - "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.6.3" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" - }, - "time": "2022-05-04T15:20:40+00:00" - }, - { - "name": "rector/rector", - "version": "0.12.23", - "source": { - "type": "git", - "url": "https://github.com/rectorphp/rector.git", - "reference": "690b31768b322db886b35845f8452025eba2cacb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", - "reference": "690b31768b322db886b35845f8452025eba2cacb", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0", - "phpstan/phpstan": "^1.6" - }, - "conflict": { - "phpstan/phpdoc-parser": "<1.2", - "rector/rector-cakephp": "*", - "rector/rector-doctrine": "*", - "rector/rector-laravel": "*", - "rector/rector-nette": "*", - "rector/rector-phpoffice": "*", - "rector/rector-phpunit": "*", - "rector/rector-prefixed": "*", - "rector/rector-symfony": "*" - }, - "bin": [ - "bin/rector" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "0.12-dev" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Instant Upgrade and Automated Refactoring of any PHP code", - "support": { - "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/0.12.23" - }, - "funding": [ - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2022-05-01T15:50:16+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "7.2.0", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", - "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.2 || ^8.0", - "phpstan/phpdoc-parser": "^1.5.1", - "squizlabs/php_codesniffer": "^3.6.2" - }, - "require-dev": { - "phing/phing": "2.17.3", - "php-parallel-lint/php-parallel-lint": "1.3.2", - "phpstan/phpstan": "1.4.10|1.6.7", - "phpstan/phpstan-deprecation-rules": "1.0.0", - "phpstan/phpstan-phpunit": "1.0.0|1.1.1", - "phpstan/phpstan-strict-rules": "1.2.3", - "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "7.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2022-05-06T10:58:42+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.8", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", - "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-04-26T13:22:23+00:00" - }, - { - "name": "symplify/easy-coding-standard", - "version": "10.2.6", - "source": { - "type": "git", - "url": "https://github.com/symplify/easy-coding-standard.git", - "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", - "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "conflict": { - "friendsofphp/php-cs-fixer": "<3.0", - "squizlabs/php_codesniffer": "<3.6" - }, - "bin": [ - "bin/ecs" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "9.5-dev" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Prefixed scoped version of ECS package", - "support": { - "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" - }, - "funding": [ - { - "url": "https://www.paypal.me/rectorphp", - "type": "custom" - }, - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2022-05-17T07:11:50+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.3.0" -} diff --git a/app/config/container.php b/app/config/container.php deleted file mode 100644 index ceb20dc..0000000 --- a/app/config/container.php +++ /dev/null @@ -1,23 +0,0 @@ -addDefinitions([ - ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), - ResponseInterface::class => fn () => new Response, - Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), - Clock::class => fn () => new SystemClock, -]); - -return $builder->build(); diff --git a/app/config/routes.php b/app/config/routes.php deleted file mode 100644 index c9b632d..0000000 --- a/app/config/routes.php +++ /dev/null @@ -1,10 +0,0 @@ -addRoute('GET', '/hello[/{name}]', Hello::class); - $r->addRoute('GET', '/other', Other::class); -}; diff --git a/app/ecs.php b/app/ecs.php deleted file mode 100644 index 6742326..0000000 --- a/app/ecs.php +++ /dev/null @@ -1,89 +0,0 @@ -parallel(); - $config->paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); - $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); - - $config->sets([ - SetList::PSR_12, - SetList::STRICT, - SetList::ARRAY, - SetList::SPACES, - SetList::DOCBLOCK, - SetList::CLEAN_CODE, - SetList::COMMON, - SetList::COMMENTS, - SetList::NAMESPACES, - SetList::SYMPLIFY, - SetList::CONTROL_STRUCTURES, - ]); - - // force visibility declaration on class constants - $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ - 'fixable' => true, - ]); - - // sort all use statements - $config->rules([ - AlphabeticallySortedUsesSniff::class, - DisallowGroupUseSniff::class, - MultipleUsesPerLineSniff::class, - NamespaceSpacingSniff::class, - ]); - - // import all namespaces, and event php core functions and classes - $config->ruleWithConfiguration( - ReferenceUsedNamesOnlySniff::class, - [ - 'allowFallbackGlobalConstants' => false, - 'allowFallbackGlobalFunctions' => false, - 'allowFullyQualifiedGlobalClasses' => false, - 'allowFullyQualifiedGlobalConstants' => false, - 'allowFullyQualifiedGlobalFunctions' => false, - 'allowFullyQualifiedNameForCollidingClasses' => true, - 'allowFullyQualifiedNameForCollidingConstants' => true, - 'allowFullyQualifiedNameForCollidingFunctions' => true, - 'searchAnnotations' => true, - ] - ); - - // define newlines between use statements - $config->ruleWithConfiguration(UseSpacingSniff::class, [ - 'linesCountBeforeFirstUse' => 1, - 'linesCountBetweenUseTypes' => 1, - 'linesCountAfterLastUse' => 1, - ]); - - // strict types declaration should be on same line as opening tag - $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ - 'declareOnFirstLine' => true, - 'spacesCountAroundEqualsSign' => 0, - ]); - - // disallow ?Foo typehint in favor of Foo|null - $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ - 'withSpaces' => 'no', - 'shortNullable' => 'no', - 'nullPosition' => 'last', - ]); - - // Remove useless parentheses in new statements - $config->rule(NewWithoutParenthesesSniff::class); -}; diff --git a/app/phpstan-baseline.neon b/app/phpstan-baseline.neon deleted file mode 100644 index 38383b9..0000000 --- a/app/phpstan-baseline.neon +++ /dev/null @@ -1,6 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: src/Bootstrap.php diff --git a/app/phpstan.neon b/app/phpstan.neon deleted file mode 100644 index 2eac45a..0000000 --- a/app/phpstan.neon +++ /dev/null @@ -1,8 +0,0 @@ -includes: - - phpstan-baseline.neon - -parameters: - level: max - paths: - - src - - config \ No newline at end of file diff --git a/app/public/index.php b/app/public/index.php deleted file mode 100644 index 970d132..0000000 --- a/app/public/index.php +++ /dev/null @@ -1,3 +0,0 @@ -paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); - - $rectorConfig->importNames(); - - $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); -}; diff --git a/app/src/Action/Hello.php b/app/src/Action/Hello.php deleted file mode 100644 index ec2e00c..0000000 --- a/app/src/Action/Hello.php +++ /dev/null @@ -1,32 +0,0 @@ -getAttribute('name', 'Stranger'); - $body = $this->response->getBody(); - - $time = $this->clock->now() - ->format('H:i:s'); - - $body->write('Hello ' . $name . '!
'); - $body->write('The Time is: ' . $time); - - return $this->response->withBody($body) - ->withStatus(200); - } -} diff --git a/app/src/Action/Other.php b/app/src/Action/Other.php deleted file mode 100644 index c42c74b..0000000 --- a/app/src/Action/Other.php +++ /dev/null @@ -1,24 +0,0 @@ -response->getBody(); - - $body->write('This works too!'); - - return $this->response->withBody($body) - ->withStatus(200); - } -} diff --git a/app/src/Bootstrap.php b/app/src/Bootstrap.php deleted file mode 100644 index 023c8c0..0000000 --- a/app/src/Bootstrap.php +++ /dev/null @@ -1,104 +0,0 @@ -pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $t) { - error_log('ERROR: ' . $t->getMessage(), $t->getCode()); - echo 'Oooopsie'; - }); -} - -$whoops->register(); - -$container = require __DIR__ . '/../config/container.php'; -assert($container instanceof ContainerInterface); - -$request = $container->get(ServerRequestInterface::class); -assert($request instanceof ServerRequestInterface); - -$dispatcher = $container->get(Dispatcher::class); -assert($dispatcher instanceof Dispatcher); - - -$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); - -try { - switch ($routeInfo[0]) { - case Dispatcher::FOUND: - $className = $routeInfo[1]; - $handler = $container->get($className); - assert($handler instanceof RequestHandlerInterface); - foreach ($routeInfo[2] as $attributeName => $attributeValue) { - $request = $request->withAttribute($attributeName, $attributeValue); - } - $response = $handler->handle($request); - break; - case Dispatcher::METHOD_NOT_ALLOWED: - throw new MethodNotAllowed; - case Dispatcher::NOT_FOUND: - default: - throw new NotFound; - } -} catch (MethodNotAllowed) { - $response = (new Response)->withStatus(405); - $response->getBody() - ->write('Method not Allowed'); -} catch (NotFound) { - $response = (new Response)->withStatus(404); - $response->getBody() - ->write('Not Found'); -} catch (Throwable $t) { - throw new InternalServerError($t->getMessage(), $t->getCode(), $t); -} - -foreach ($response->getHeaders() as $name => $values) { - $first = strtolower($name) !== 'set-cookie'; - foreach ($values as $value) { - $header = sprintf('%s: %s', $name, $value); - header($header, $first); - $first = false; - } -} - -$statusLine = sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() -); -header($statusLine, true, $response->getStatusCode()); - -echo $response->getBody(); diff --git a/app/src/Exception/InternalServerError.php b/app/src/Exception/InternalServerError.php deleted file mode 100644 index 9c3b369..0000000 --- a/app/src/Exception/InternalServerError.php +++ /dev/null @@ -1,9 +0,0 @@ - Date: Sat, 21 May 2022 01:19:14 +0200 Subject: [PATCH 303/314] fix some typos --- 03-error-handler.md | 4 ++-- 04-development-helpers.md | 24 ++++++++++++------------ 05-http.md | 12 ++++++------ 06-router.md | 8 ++++---- 07-dispatching-to-a-class.md | 10 +++++----- 08-inversion-of-control.md | 2 +- 09-dependency-injector.md | 22 +++++++++++----------- 7 files changed, 41 insertions(+), 41 deletions(-) diff --git a/03-error-handler.md b/03-error-handler.md index d1d7c06..099e34b 100644 --- a/03-error-handler.md +++ b/03-error-handler.md @@ -23,7 +23,7 @@ like this: }, ``` -Now run `composer update` in your console and it will be installed. +Now run `composer update` in your console, and it will be installed. Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, i that case composer automatically installs the package and updates your composer.json-file. @@ -33,7 +33,7 @@ ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer alread only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Bootstrap.php`. **Important:** Never show any errors in your production environment. A stack trace or even just a simple error message -can help someone to gain access to your system. Always show a user friendly error page instead and send an email to +can help someone to gain access to your system. Always show a user-friendly error page instead and send an email to yourself, write to a log or something similar. So only you can see the errors in the production environment. For development that does not make sense though -- you want a nice error page. The solution is to have an environment diff --git a/04-development-helpers.md b/04-development-helpers.md index b9fcd50..595bb3d 100644 --- a/04-development-helpers.md +++ b/04-development-helpers.md @@ -4,7 +4,7 @@ I have added some more helpers to my composer.json that help me with development. As these are scripts and programms used only for development they should not be used in a production environment. Composer has a specific sections in its -file called "dev-dependencies", everything that is required in this section does not get installen in production. +file called "dev-dependencies", everything that is required in this section does not get installed in production. Let's install our dev-helpers and i will explain them one by one: `composer require --dev phpstan/phpstan symfony/var-dumper slevomat/coding-standard symplify/easy-coding-standard rector/rector` @@ -15,7 +15,7 @@ Phpstan is a great little tool, that tries to understand your code and checks if create bad defined interfaces and structures. It also helps in finding logic-errors, dead code, access to array elements that are not (or not always) available, if-statements that always are true and a lot of other stuff. -A very simple example would be a small functions that takes a DateTime-Object and prints it in a human readable format. +A very simple example would be a small functions that takes a DateTime-Object and prints it in a human-readable format. ```php /** @@ -45,9 +45,9 @@ Line Bootstrap.php ``` The second error is something that "declare strict-types" already catches for us, but the first error is something that -we usually would not discover easily without speccially looking for this errortype. +we usually would not discover easily without specially looking for this error-type. -We can add a simple configfile called `phpstan.neon` to our project so that we do not have to specify the errorlevel and +We can add a simple config file called `phpstan.neon` to our project so that we do not have to specify the error level and path everytime we want to check our code for errors: ```yaml @@ -59,7 +59,7 @@ parameters: now we can just call `./vendor/bin/phpstan analyze` and have the same setting for every developer working in our project With this settings we have already a great setup to catch some errors before we execute the code, but it still allows us -some silly things, therefore we want to add install some packages that enforce rules that are a little bit more strict. +some silly things, therefore we want to add install some packages that enforce rules that are a little stricter. ```shell composer require --dev phpstan/extension-installer @@ -67,7 +67,7 @@ composer require --dev phpstan/phpstan-strict-rules thecodingmachine/phpstan-str ``` During the first install you need to allow the extension installer to actually install the extension. The second command -installs some more strict rulesets and activates them in phpstan. +installs some more strict rules and activates them in phpstan. If we now rerun phpstan it already tells us about some errors we have made: @@ -84,9 +84,9 @@ Line Bootstrap.php ``` The last two Errors are caused by the Exception we have used to test the ErrorHandler in the last chapter if we remove -that we should be able to fix that. The first error is something we could fix, but I dont want to focus on that specific +that we should be able to fix that. The first error is something we could fix, but I don't want to focus on that specific problem right now. Phpstan gives us the option to ignore some errors and handle them later. If for example we are working -on an old legacy codebase and wanted to add static analysis to it but cant because we would get 1 Million error messages +on an old legacy codebase and wanted to add static analysis to it but can't because we would get 1 Million error messages everytime we use phpstan, we could add all those errors to a list and tell phpstan to only bother us about new errors we are adding to our code. @@ -128,7 +128,7 @@ which basically does the same has in my experience some more Rules available tha But we are going to use neither of those tools directly and instead choose the [Easy Coding Standard](https://github.com/symplify/easy-coding-standard) which allows us to combine rules from both mentioned tools, and also claims to run much faster. You could check out the documentation and decide on your own coding standard. Or use the one provided by me, which is base on PSR-12 but adds -some highly opiniated options. First create a file 'ecs.php' and either add your own configuration or copy the my +some highly opiniated options. First create a file 'ecs.php' and either add your own configuration or copy the prepared one: ```php @@ -288,8 +288,8 @@ with lots of parameters by hand all the time, so I added a few lines to my `comp }, ``` -that way I can just type "composer" followed by the command name in the root of my project. if i want to start the -php devserver I can just type "composer serve" and dont have to type in the hostname, port and targetdirectory all the +that way I can just type "composer" followed by the command name in the root of my project. if I want to start the +php dev server I can just type "composer serve" and don't have to type in the hostname, port and target directory all the time. You could also configure PhpStorm to automatically run these commands in the background and highlight the violations @@ -297,7 +297,7 @@ directly in the file you are currently editing. I personally am not a fan of thi flow when programming and always forces me to be absolutely strict even if I am only trying out an idea for debugging. My workflow is to just write my code the way I currently feel and that execute the phpstan and the fix scripts before -commiting and pushing the code. There is a [highly opiniated blogpost](https://tomasvotruba.com/blog/2019/06/24/do-you-use-php-codesniffer-and-php-cs-fixer-phpstorm-plugin-you-are-slow-and-expensive/) +committing and pushing the code. There is a [highly opiniated blogpost](https://tomasvotruba.com/blog/2019/06/24/do-you-use-php-codesniffer-and-php-cs-fixer-phpstorm-plugin-you-are-slow-and-expensive/) discussing that topic further. That you can read. But in the end it boils down to what you are most comfortable with. [<< previous](03-error-handler.md) | [next >>](05-http.md) \ No newline at end of file diff --git a/05-http.md b/05-http.md index 61077a6..c7a3f05 100644 --- a/05-http.md +++ b/05-http.md @@ -19,7 +19,7 @@ the laminas/laminas-diactoros package as i am an old time fan of the laminas (pr Some alternatives are [slim-psr7](https://github.com/slimphp/Slim-Psr7), [Guzzle](https://github.com/guzzle/psr7) and a [lot more](https://packagist.org/providers/psr/http-message-implementation) are available for you to choose from. -Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore i will not use +Symfony ships its own Request and Response objects that do not implement the psr-7 interfaces. Therefore, I will not use that in this tutorial, but if you understand how the psr-7 interfaces work you should have no problem in understanding the [symfony http-foundation](https://symfony.com/doc/current/components/http_foundation.html#request). @@ -38,7 +38,7 @@ $response->getBody()->write('The Uri is: ' . $request->getUri()->getPath()); This sets up the `Request` and `Response` objects that you can use in your other classes to get request data and send a response back to the browser. -In order to actually add content to the response you have to access the Body-Streamobject of the Response and use the +In order to actually add content to the response you have to access the body stream object of the Response and use the write()-Method on that object. @@ -66,7 +66,7 @@ $response = $response->withStatus(200); $response = $response->withAddedHeader('Content-type', 'application/json'); ``` -If you have ever struggled with Mutationproblems in an DateTime-Object you might understand why the standard has been +If you have ever struggled with Mutation-problems in an DateTime-Object you might understand why the standard has been defined this way. But if you have been keeping attention you might argue that the following line should not work if the request object is @@ -80,7 +80,7 @@ The response-body implements a stream interface which is immutable for some reas [meta-document](https://www.php-fig.org/psr/psr-7/meta/#why-are-streams-mutable). For me the important thing is to be aware of the problems that can occur with mutable objects. Here is a small [Blogpost](http://andrew.carterlunn.co.uk/programming/2016/05/22/psr-7-is-not-immutable.html) that gives some context. Beware that the Middleware-Example in the post is based on a deprecated middleware standard. But more on middlewares will be discussed in later chapters. -I for one am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content +I, for one, am happy about that fact, as it saves me from writing at least 3 lines of code whenever i want to add content to a response object. ```php @@ -90,7 +90,7 @@ $response = $response->withBody($body); ``` Right now we are just outputting the Response-Body without any headers or http-status. So we need to expand our -output-logic a little bit more. Replace the line that echos the response-body with the following: +output-logic a little more. Replace the line that echos the response-body with the following: ```php foreach ($response->getHeaders() as $name => $values) { @@ -114,7 +114,7 @@ echo $response->getBody(); ``` This code is still fairly simple and there is a lot more stuff that can be considered when emitting a response to a -webbrowser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. +browser, if you want a more complete solution you can take a look at the [httpsoft/http-emitter](https://github.com/httpsoft/http-emitter/blob/master/src/SapiEmitter.php) package on github. Remember that the object is only storing data, so if you set multiple status codes before you send the response, only the last one will be applied. diff --git a/06-router.md b/06-router.md index 10a0c93..66c08fa 100644 --- a/06-router.md +++ b/06-router.md @@ -63,10 +63,10 @@ dispatcher gets called and the appropriate part of the switch statement will be we collect any variable parameters of the route, store them in the request parameterbag and call the handler callable. If the route dispatcher returns a wrong value in the first entry of the routeMatch array we handle it the same as a 404. -This setup might work for really small applications, but once you start adding a few routes your bootstrap file will +This setup might work for tiny applications, but once you start adding a few routes your bootstrap file will quickly get cluttered. So let's move them out into a separate file. -Create a new directory in you projectroot named 'config' and add a 'routes.php' file with the following content; +Create a new directory in you project root named 'config' and add a 'routes.php' file with the following content; ```php >](07-dispatching-to-a-class.md) diff --git a/07-dispatching-to-a-class.md b/07-dispatching-to-a-class.md index 830a30f..148f67b 100644 --- a/07-dispatching-to-a-class.md +++ b/07-dispatching-to-a-class.md @@ -33,8 +33,8 @@ final class Hello implements \Psr\Http\Server\RequestHandlerInterface ``` You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) -that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is -fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement +that has a 'handle'-Method with requires a Request object as its parameter and returns a Response-object. For now this is +fine, but we may have to change our approach later. In any way it is good to know about this interface as we will implement it in some other parts of our application as well. In order to use that Interface we have to require it with composer: `composer require psr/http-server-handler`. @@ -77,9 +77,9 @@ Now if you visit `http://localhost:1235/` everything should work. If not, go bac And of course don't forget to commit your changes. Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still -generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for +generated in the routing-matching section and not in special classes. Also, we have still left some cases to chance, for example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still -have our whoopsie error-handler but i like to be more explicit in my control flow. +have our whoopsie error-handler, but I like to be more explicit in my control flow. In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and @@ -99,7 +99,7 @@ final class NotFound extends Exception{} Use that example to create a MethodNotAllowedException.php and InternalServerErrorException.php as well. -After you have created those we update our Routercode to use the new Exceptions. +After you have created those we update our Router code to use the new Exceptions. ```php try { diff --git a/08-inversion-of-control.md b/08-inversion-of-control.md index 21f4f23..e9b1e90 100644 --- a/08-inversion-of-control.md +++ b/08-inversion-of-control.md @@ -7,7 +7,7 @@ want to switch to a more powerfull Http-Implementation later, or need to create we would need to edit every one of our request handlers to call a different constructor of the class. The sane option is to use [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control). This means that -instead of giving the class the responsiblity of creating the object it needs, you just ask for them. This is done +instead of giving the class the responsibility of creating the object it needs, you just ask for them. This is done with [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). If this sounds a little complicated right now, don't worry. Just follow the tutorial and once you see how it is diff --git a/09-dependency-injector.md b/09-dependency-injector.md index 060a09f..01ff14f 100644 --- a/09-dependency-injector.md +++ b/09-dependency-injector.md @@ -4,13 +4,13 @@ In the last chapter we rewrote our Actions to require the response-objet as a constructor parameter, and provided it in the dispatcher section of our `Bootstrap.php`. As we only have one dependency this works really fine, but if we have -different classes with different dependencies our bootstrap file gets complicated quite quickly. Lets look at an example +different classes with different dependencies our bootstrap file gets complicated quite quickly. Let's look at an example to explain the problem and work on a solution. #### Adding a clock service Lets assume that we want to show the current time in our Hello action. We could easily just call use one of the many -ways to get the current time directly in the handle-method, but lets create a separate class and interface for that so +ways to get the current time directly in the handle-method, but let's create a separate class and interface for that so we can later configure and switch our implementation. We need a new 'Service\Time' namespace, so lets first create the folder in our 'src' directory 'src/Service/Time'. @@ -45,7 +45,7 @@ final class SystemClock implements Clock } ``` -Now we can require the Clockinterface as a depencency in our controller and use it to display the current time. +Now we can require the Clock interface as a dependency in our controller and use it to display the current time. ```php Too few arguments to function Lubian\NoFramework\Action\Hello::__construct(), 1 passed in /home/lubiana/PhpstormProjects/no-framework/app/src/Bootstrap.php on line 62 and exactly 2 expected Our current problem is, that we have two Actions defined, which both have different constructor requirements. That means, @@ -114,7 +114,7 @@ I mostly define an Interface or a fully qualified classname as an ID. That way I the Clock interface or an Action class and get an object of that class or an object implementing the given Interface. For the sake of this tutorial we will put a new file in our config folder that returns an anonymous class implementing -the containerinterface. +the container-interface. In this class we will configure all services required for our application and make them accessible via the get($id) method. @@ -164,13 +164,13 @@ return new class () implements \Psr\Container\ContainerInterface { }; ``` -Here I have declared a services array, that has a class- or interfacename as the keys, and the values are short +Here I have declared a services array, that has a class- or interface name as the keys, and the values are short closures that return an Object of the defined class or interface. The `has` method simply checks if the given id is defined in our services array, and the `get` method calls the closure defined in the array for the given id key and then returns the result of that closure. To use the container we need to update our Bootstrap.php. Firstly we need to get an instance of our container, and then -use that to create our Request-Object as well as the Dispatcher. So remove the manual instantion of those objects and +use that to create our Request-Object as well as the Dispatcher. So remove the manual instantiation of those objects and replace that with the following code: ```php @@ -201,7 +201,7 @@ assert($handler instanceof RequestHandlerInterface); If you now open the `/hello` route in your browser everything should work again! -#### Using Autowiring +#### Using Auto wiring If you take a critical look at the services array you might see that we need to manually define how our Hello- and Other-Action are getting constructed. This is quite repetitive, as we have already declared what objects to create @@ -215,9 +215,9 @@ functionality ourselves, or just try to use a library that takes care of that fo You can query the composer database to find all [libraries that implment the container interface](https://packagist.org/providers/psr/container-implementation). I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) -out of the box, and also solves the autowiring problem. +out of the box, and also solves the auto wiring problem. -Lets rewrite our `container.php` file to use the PHP-DI container and only define the Services the Container cannot +Let's rewrite our `container.php` file to use the PHP-DI container and only define the Services the Container cannot automatically build. ```php @@ -237,7 +237,7 @@ return $builder->build(); ``` As the PHP-DI container that is return by the `$builder->build()` method implements the same container interface as our -previously used ad-hoc container we won't need to update the our Bootstrap file and everything still works. +previously used ad-hoc container we won't need to update the Bootstrap file and everything still works. [<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md) \ No newline at end of file From e35270a766768c3d6fdbbfb0a7ae1896755481f2 Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 23 May 2022 08:43:33 +0200 Subject: [PATCH 304/314] fix type in DI chapter --- 09-dependency-injector.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/09-dependency-injector.md b/09-dependency-injector.md index 01ff14f..14652ff 100644 --- a/09-dependency-injector.md +++ b/09-dependency-injector.md @@ -212,7 +212,7 @@ PHP provides us with the great Reflection Api that is capable of showing us, [wh given class requires](https://www.php.net/manual/de/reflectionclass.getconstructor.php]. We could implement that functionality ourselves, or just try to use a library that takes care of that for us. -You can query the composer database to find all [libraries that implment the container interface](https://packagist.org/providers/psr/container-implementation). +You can query the composer database to find all [libraries that implement the container interface](https://packagist.org/providers/psr/container-implementation). I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring) out of the box, and also solves the auto wiring problem. @@ -223,7 +223,6 @@ automatically build. ```php addDefinitions([ From c6cb68c98a1fee8bb1b54c24b043bb05ee7711c2 Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 23 May 2022 14:45:47 +0200 Subject: [PATCH 305/314] add solutions for chapter 9 and fix urltypos --- 09-dependency-injector.md | 10 ++++++---- implementation/09/composer.lock | 2 +- implementation/09/config/container.php | 16 ++++++++-------- implementation/09/src/Action/Hello.php | 7 ++----- implementation/09/src/Bootstrap.php | 1 - 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/09-dependency-injector.md b/09-dependency-injector.md index 14652ff..793fb06 100644 --- a/09-dependency-injector.md +++ b/09-dependency-injector.md @@ -16,7 +16,6 @@ we can later configure and switch our implementation. We need a new 'Service\Time' namespace, so lets first create the folder in our 'src' directory 'src/Service/Time'. There we place a Clock.php interface and a SystemClock.php implementation: - The Clock.php interface: ```php =8.1" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.2.0" } diff --git a/implementation/09/config/container.php b/implementation/09/config/container.php index ceb20dc..84e7386 100644 --- a/implementation/09/config/container.php +++ b/implementation/09/config/container.php @@ -1,7 +1,6 @@ addDefinitions([ - ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), - ResponseInterface::class => fn () => new Response, - Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), - Clock::class => fn () => new SystemClock, -]); +$builder->addDefinitions( + [ + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + ResponseInterface::class => fn () => new Response, + FastRoute\Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + Clock::class => fn () => new SystemClock, + ] +); return $builder->build(); diff --git a/implementation/09/src/Action/Hello.php b/implementation/09/src/Action/Hello.php index ec2e00c..012f1df 100644 --- a/implementation/09/src/Action/Hello.php +++ b/implementation/09/src/Action/Hello.php @@ -11,7 +11,7 @@ final class Hello implements RequestHandlerInterface { public function __construct( private readonly ResponseInterface $response, - private readonly Clock $clock + private readonly Clock $clock, ) { } @@ -20,11 +20,8 @@ final class Hello implements RequestHandlerInterface $name = $request->getAttribute('name', 'Stranger'); $body = $this->response->getBody(); - $time = $this->clock->now() - ->format('H:i:s'); - $body->write('Hello ' . $name . '!
'); - $body->write('The Time is: ' . $time); + $body->write('The time is: ' . $this->clock->now()->format('H:i:s')); return $this->response->withBody($body) ->withStatus(200); diff --git a/implementation/09/src/Bootstrap.php b/implementation/09/src/Bootstrap.php index 023c8c0..c5237b8 100644 --- a/implementation/09/src/Bootstrap.php +++ b/implementation/09/src/Bootstrap.php @@ -52,7 +52,6 @@ assert($request instanceof ServerRequestInterface); $dispatcher = $container->get(Dispatcher::class); assert($dispatcher instanceof Dispatcher); - $routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); try { From f54c5764417cc26b28a3d9726c656c92043fa2d6 Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 23 May 2022 22:06:21 +0200 Subject: [PATCH 306/314] add small typo and wording improvements to chapters 9 and 10, update name of time service --- 09-dependency-injector.md | 1 - 10-invoker.md | 43 +++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/09-dependency-injector.md b/09-dependency-injector.md index 793fb06..c35b99b 100644 --- a/09-dependency-injector.md +++ b/09-dependency-injector.md @@ -121,7 +121,6 @@ the container-interface. In this class we will configure all services required for our application and make them accessible via the get($id) method. -p Before we can implement the interface we need to install its definition with composer `composer require "psr/container:^1.0"`. now we can create a file with a Class that implements that interface. diff --git a/10-invoker.md b/10-invoker.md index 0ed6b59..4fa49aa 100644 --- a/10-invoker.md +++ b/10-invoker.md @@ -2,13 +2,13 @@ ### Invoker -Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the +Currently, all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long -with some services and not the whole Requestobject with all its various properties. +with some services and not the whole Request object with all its various properties. -If we take our Hello action for example we only need a response object, the time service and the 'name' information from -the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named -the class hello and it would be redundant to also call the the method hello. So an updated version of that class could +If we take our Hello action for example we only need a response object, the clock service and the 'name' information from +the request-uri. And as that class only provides one simple method we could easily make that invokable as we already named +the class hello, and it would be redundant to also call the method hello. So an updated version of that class could look like this: ```php @@ -16,17 +16,16 @@ final class Hello { public function __invoke( ResponseInterface $response, - Now $now, - string $name = 'Stranger', + Clock $clock, + string $name = 'Stranger' ): ResponseInterface { - $body = $this->response->getBody(); - $nowString = $now->get()->format('H:i:s'); - - $body->write('Hello ' . $name . '!'); - $body->write(' The Time is ' . $nowString); - return $response - ->withBody($body) + $body = $response->getBody(); + + $body->write('Hello ' . $name . '!
'); + $body->write('The time is: ' . $clock->now()->format('H:i:s')); + + return $response->withBody($body) ->withStatus(200); } } @@ -34,17 +33,17 @@ final class Hello It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the -rootpath of our application yet. +root path of our application yet. ```php $r->addRoute('GET', '/hello[/{name}]', Hello::class); -$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']); +$r->addRoute('GET', '/other-route', [Other::class, 'handle']); $r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello')); ``` -In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the -route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that -class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what +In order to support this crazy route definitions we would need to write a lot of code for actually calling the result of +the route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of +that class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple callable we would need to manually use reflection as well to resolve all the arguments. @@ -68,13 +67,13 @@ $response = $container->call($handler, $args); Try to open [localhost:1235/](http://localhost:1235/) in your browser and check if you are getting redirected to '/hello'. But by now you should know that I do not like to depend on specific implementations and the call method is not defined in -the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container +the psr/container interface. Therefore, we would not be able to use that if we are ever switching to the symfony container or any other implementation. Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific -container implementation so we could use it with any container that implements the ContainerInterface. And best of all +container implementation, so we could use it with any container that implements the ContainerInterface. And best of all the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that -we could implement if we ever want to write our own implementation or we could write an adapter that uses a different +we could implement if we ever want to write our own implementation, or we could write an adapter that uses a different class that solves the same problem. But for now we are using the solution provided by PHP-DI. From b139a9dcc3541044c60c5db2ab89f19e3863c657 Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 23 May 2022 22:08:50 +0200 Subject: [PATCH 307/314] add solutions for chapter 10 --- implementation/10/composer.json | 50 + implementation/10/composer.lock | 1466 +++++++++++++++++ implementation/10/config/container.php | 23 + implementation/10/config/routes.php | 12 + implementation/10/ecs.php | 89 + implementation/10/phpstan-baseline.neon | 6 + implementation/10/phpstan.neon | 8 + implementation/10/public/favicon.ico | Bin 0 -> 15086 bytes implementation/10/public/index.php | 3 + implementation/10/rector.php | 12 + implementation/10/src/Action/Hello.php | 24 + implementation/10/src/Action/Other.php | 24 + implementation/10/src/Bootstrap.php | 105 ++ .../10/src/Exception/InternalServerError.php | 9 + .../10/src/Exception/MethodNotAllowed.php | 9 + implementation/10/src/Exception/NotFound.php | 9 + implementation/10/src/Service/Time/Clock.php | 10 + .../10/src/Service/Time/SystemClock.php | 13 + 18 files changed, 1872 insertions(+) create mode 100644 implementation/10/composer.json create mode 100644 implementation/10/composer.lock create mode 100644 implementation/10/config/container.php create mode 100644 implementation/10/config/routes.php create mode 100644 implementation/10/ecs.php create mode 100644 implementation/10/phpstan-baseline.neon create mode 100644 implementation/10/phpstan.neon create mode 100644 implementation/10/public/favicon.ico create mode 100644 implementation/10/public/index.php create mode 100644 implementation/10/rector.php create mode 100644 implementation/10/src/Action/Hello.php create mode 100644 implementation/10/src/Action/Other.php create mode 100644 implementation/10/src/Bootstrap.php create mode 100644 implementation/10/src/Exception/InternalServerError.php create mode 100644 implementation/10/src/Exception/MethodNotAllowed.php create mode 100644 implementation/10/src/Exception/NotFound.php create mode 100644 implementation/10/src/Service/Time/Clock.php create mode 100644 implementation/10/src/Service/Time/SystemClock.php diff --git a/implementation/10/composer.json b/implementation/10/composer.json new file mode 100644 index 0000000..335b122 --- /dev/null +++ b/implementation/10/composer.json @@ -0,0 +1,50 @@ +{ + "name": "lubian/no-framework", + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "example", + "email": "test@example.com" + } + ], + "require": { + "php": ">=8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.11", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "psr/container": "^1.0", + "php-di/php-di": "^6.4" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/implementation/10/composer.lock b/implementation/10/composer.lock new file mode 100644 index 0000000..d2d0350 --- /dev/null +++ b/implementation/10/composer.lock @@ -0,0 +1,1466 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "0b6833b8fa6869bd212824769648e667", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-05-17T10:57:52+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "pestphp/pest": "^1.18", + "phpstan/phpstan": "^0.12.98", + "symfony/var-dumper": "^5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2022-05-16T17:09:47+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.4.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "shasum": "" + }, + "require": { + "laravel/serializable-closure": "^1.0", + "php": ">=7.4.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.10", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.11.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^9.5" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.4.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2022-04-09T16:46:38+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/implementation/10/config/container.php b/implementation/10/config/container.php new file mode 100644 index 0000000..84e7386 --- /dev/null +++ b/implementation/10/config/container.php @@ -0,0 +1,23 @@ +addDefinitions( + [ + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + ResponseInterface::class => fn () => new Response, + FastRoute\Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + Clock::class => fn () => new SystemClock, + ] +); + +return $builder->build(); diff --git a/implementation/10/config/routes.php b/implementation/10/config/routes.php new file mode 100644 index 0000000..23ff0ef --- /dev/null +++ b/implementation/10/config/routes.php @@ -0,0 +1,12 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/other', [Other::class, 'handle']); + $r->addRoute('GET', '/', fn (RI $r) => $r->withStatus(302)->withHeader('Location', '/hello')); +}; diff --git a/implementation/10/ecs.php b/implementation/10/ecs.php new file mode 100644 index 0000000..6742326 --- /dev/null +++ b/implementation/10/ecs.php @@ -0,0 +1,89 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/implementation/10/phpstan-baseline.neon b/implementation/10/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/implementation/10/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/implementation/10/phpstan.neon b/implementation/10/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/10/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/10/public/favicon.ico b/implementation/10/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/10/public/index.php b/implementation/10/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/10/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/implementation/10/src/Action/Hello.php b/implementation/10/src/Action/Hello.php new file mode 100644 index 0000000..379ea73 --- /dev/null +++ b/implementation/10/src/Action/Hello.php @@ -0,0 +1,24 @@ +getBody(); + + $body->write('Hello ' . $name . '!
'); + $body->write('The time is: ' . $clock->now()->format('H:i:s')); + + return $response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/10/src/Action/Other.php b/implementation/10/src/Action/Other.php new file mode 100644 index 0000000..c42c74b --- /dev/null +++ b/implementation/10/src/Action/Other.php @@ -0,0 +1,24 @@ +response->getBody(); + + $body->write('This works too!'); + + return $this->response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/10/src/Bootstrap.php b/implementation/10/src/Bootstrap.php new file mode 100644 index 0000000..26fd2ea --- /dev/null +++ b/implementation/10/src/Bootstrap.php @@ -0,0 +1,105 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +$container = require __DIR__ . '/../config/container.php'; +assert($container instanceof ContainerInterface); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$dispatcher = $container->get(Dispatcher::class); +assert($dispatcher instanceof Dispatcher); + +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $routeInfo[2]['request'] = $request; + assert($container instanceof InvokerInterface); + $response = $container->call($routeInfo[1], $routeInfo[2]); + assert($response instanceof ResponseInterface); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody() + ->write('Method not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody() + ->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/10/src/Exception/InternalServerError.php b/implementation/10/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/10/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ + Date: Mon, 30 May 2022 14:27:48 +0200 Subject: [PATCH 308/314] update chapter 10 solutions --- implementation/10/config/routes.php | 4 ++-- implementation/10/src/Action/Hello.php | 3 +-- implementation/10/src/Bootstrap.php | 10 ++++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/implementation/10/config/routes.php b/implementation/10/config/routes.php index 23ff0ef..b68712f 100644 --- a/implementation/10/config/routes.php +++ b/implementation/10/config/routes.php @@ -3,10 +3,10 @@ use FastRoute\RouteCollector; use Lubian\NoFramework\Action\Hello; use Lubian\NoFramework\Action\Other; -use Psr\Http\Message\ResponseInterface as RI; +use Psr\Http\Message\ResponseInterface; return function (RouteCollector $r) { $r->addRoute('GET', '/hello[/{name}]', Hello::class); $r->addRoute('GET', '/other', [Other::class, 'handle']); - $r->addRoute('GET', '/', fn (RI $r) => $r->withStatus(302)->withHeader('Location', '/hello')); + $r->addRoute('GET', '/', fn (ResponseInterface $r) => $r->withHeader('Location', '/hello') ->withStatus(302)); }; diff --git a/implementation/10/src/Action/Hello.php b/implementation/10/src/Action/Hello.php index 379ea73..dfb8df8 100644 --- a/implementation/10/src/Action/Hello.php +++ b/implementation/10/src/Action/Hello.php @@ -11,8 +11,7 @@ final class Hello ResponseInterface $response, Clock $clock, string $name = 'Stranger' - ): ResponseInterface - { + ): ResponseInterface { $body = $response->getBody(); $body->write('Hello ' . $name . '!
'); diff --git a/implementation/10/src/Bootstrap.php b/implementation/10/src/Bootstrap.php index 26fd2ea..8acaa81 100644 --- a/implementation/10/src/Bootstrap.php +++ b/implementation/10/src/Bootstrap.php @@ -11,7 +11,6 @@ use Lubian\NoFramework\Exception\NotFound; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Server\RequestHandlerInterface; use Throwable; use Whoops\Handler\PrettyPageHandler; use Whoops\Run; @@ -59,12 +58,15 @@ $routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->g try { switch ($routeInfo[0]) { case Dispatcher::FOUND: + $routeTarget = $routeInfo[1]; + $args = $routeInfo[2]; foreach ($routeInfo[2] as $attributeName => $attributeValue) { $request = $request->withAttribute($attributeName, $attributeValue); } - $routeInfo[2]['request'] = $request; - assert($container instanceof InvokerInterface); - $response = $container->call($routeInfo[1], $routeInfo[2]); + $args['request'] = $request; + $invoker = $container->get(InvokerInterface::class); + assert($invoker instanceof InvokerInterface); + $response = $invoker->call($routeTarget, $args); assert($response instanceof ResponseInterface); break; case Dispatcher::METHOD_NOT_ALLOWED: From e03416ea01bf4166b71946739f1354f761cc722c Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 30 May 2022 20:39:42 +0200 Subject: [PATCH 309/314] update chapter 11 --- 11-templating.md | 85 +++--------------------------------------------- 1 file changed, 4 insertions(+), 81 deletions(-) diff --git a/11-templating.md b/11-templating.md index 7bfe1aa..dc2d765 100644 --- a/11-templating.md +++ b/11-templating.md @@ -50,7 +50,7 @@ namespace Lubian\NoFramework\Template; interface Renderer { /** @param array $data */ - public function render(string $template, array $data = []) : string; + public function render(string $template, array $data = []): string; } ``` @@ -146,91 +146,14 @@ we can then use it in the factory. In your project root folder, create a `templates` folder. In there, create a file `hello.html`. The content of the file should look like this: ``` -

Hello World

-Hello {{ name }} +

Hello {{name}}

+

The time is: {{time}}

``` Now you can go back to your `Hello` action and change the render line to `$html = $this->renderer->render('hello', $data);` Navigate to the hello page in your browser to make sure everything works. -One thing that still bothers me is the fact that we have some configuration paths scattered in our dependencies -file. We could add a simple valueobject to our code that gives us a typesafe access to our configuration -values. - -Lets create a 'Settings' class in our './src' Folder: - -```php -addDefinitions([ - Settings::class => fn () => require __DIR__ '/settings.php', - ResponseInterface::class => create(Response::class), - ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), - Renderer::class => fn (ME $me) => new Mustache($me), - MLFsl::class => fn (Settings $s) => new MLFsl($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLFsl $MLFsl) => new ME(['loader' => $MLFsl]), -]); - -return $builder->build(); -``` - - - -And as always, don't forget to commit your changes. - +Before you move on to the next chapter be sure to run our quality tools and commit your changes. [<< previous](10-invoker.md) | [next >>](12-configuration.md) From 58453b6354bd83ee1728ae3cae2516517c20d81f Mon Sep 17 00:00:00 2001 From: lubiana Date: Mon, 30 May 2022 20:40:45 +0200 Subject: [PATCH 310/314] add chapter 11 solutions --- implementation/11/composer.json | 51 + implementation/11/composer.lock | 1516 +++++++++++++++++ implementation/11/config/container.php | 36 + implementation/11/config/routes.php | 12 + implementation/11/ecs.php | 89 + implementation/11/phpstan-baseline.neon | 6 + implementation/11/phpstan.neon | 8 + implementation/11/public/favicon.ico | Bin 0 -> 15086 bytes implementation/11/public/index.php | 3 + implementation/11/rector.php | 12 + implementation/11/src/Action/Hello.php | 31 + implementation/11/src/Action/Other.php | 24 + implementation/11/src/Bootstrap.php | 107 ++ .../11/src/Exception/InternalServerError.php | 9 + .../11/src/Exception/MethodNotAllowed.php | 9 + implementation/11/src/Exception/NotFound.php | 9 + implementation/11/src/Service/Time/Clock.php | 10 + .../11/src/Service/Time/SystemClock.php | 13 + .../11/src/Template/MustacheRenderer.php | 17 + implementation/11/src/Template/Renderer.php | 11 + implementation/11/templates/hello.html | 2 + 21 files changed, 1975 insertions(+) create mode 100644 implementation/11/composer.json create mode 100644 implementation/11/composer.lock create mode 100644 implementation/11/config/container.php create mode 100644 implementation/11/config/routes.php create mode 100644 implementation/11/ecs.php create mode 100644 implementation/11/phpstan-baseline.neon create mode 100644 implementation/11/phpstan.neon create mode 100644 implementation/11/public/favicon.ico create mode 100644 implementation/11/public/index.php create mode 100644 implementation/11/rector.php create mode 100644 implementation/11/src/Action/Hello.php create mode 100644 implementation/11/src/Action/Other.php create mode 100644 implementation/11/src/Bootstrap.php create mode 100644 implementation/11/src/Exception/InternalServerError.php create mode 100644 implementation/11/src/Exception/MethodNotAllowed.php create mode 100644 implementation/11/src/Exception/NotFound.php create mode 100644 implementation/11/src/Service/Time/Clock.php create mode 100644 implementation/11/src/Service/Time/SystemClock.php create mode 100644 implementation/11/src/Template/MustacheRenderer.php create mode 100644 implementation/11/src/Template/Renderer.php create mode 100644 implementation/11/templates/hello.html diff --git a/implementation/11/composer.json b/implementation/11/composer.json new file mode 100644 index 0000000..c0fe924 --- /dev/null +++ b/implementation/11/composer.json @@ -0,0 +1,51 @@ +{ + "name": "lubian/no-framework", + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "example", + "email": "test@example.com" + } + ], + "require": { + "php": ">=8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.11", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "psr/container": "^1.0", + "php-di/php-di": "^6.4", + "mustache/mustache": "^2.14" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/implementation/11/composer.lock b/implementation/11/composer.lock new file mode 100644 index 0000000..c3de49e --- /dev/null +++ b/implementation/11/composer.lock @@ -0,0 +1,1516 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "ea5b586e05e6b1d75bded814662047b4", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-05-17T10:57:52+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "pestphp/pest": "^1.18", + "phpstan/phpstan": "^0.12.98", + "symfony/var-dumper": "^5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2022-05-16T17:09:47+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.4.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "shasum": "" + }, + "require": { + "laravel/serializable-closure": "^1.0", + "php": ">=7.4.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.10", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.11.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^9.5" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.4.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2022-04-09T16:46:38+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/11/config/container.php b/implementation/11/config/container.php new file mode 100644 index 0000000..de7b1d4 --- /dev/null +++ b/implementation/11/config/container.php @@ -0,0 +1,36 @@ +addDefinitions( + [ + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + ResponseInterface::class => fn () => new Response, + Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'), + Clock::class => fn () => new SystemClock, + Renderer::class => fn (MustacheRenderer $me) => $me, + Mustache_Loader_FilesystemLoader::class => fn () => new Mustache_Loader_FilesystemLoader( + __DIR__ . '/../templates', + [ + 'extension' => '.html', + ] + ), + Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $loader) => new Mustache_Engine([ + 'loader' => $loader, + ]), + ] +); + +return $builder->build(); diff --git a/implementation/11/config/routes.php b/implementation/11/config/routes.php new file mode 100644 index 0000000..b68712f --- /dev/null +++ b/implementation/11/config/routes.php @@ -0,0 +1,12 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/other', [Other::class, 'handle']); + $r->addRoute('GET', '/', fn (ResponseInterface $r) => $r->withHeader('Location', '/hello') ->withStatus(302)); +}; diff --git a/implementation/11/ecs.php b/implementation/11/ecs.php new file mode 100644 index 0000000..6742326 --- /dev/null +++ b/implementation/11/ecs.php @@ -0,0 +1,89 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/implementation/11/phpstan-baseline.neon b/implementation/11/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/implementation/11/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/implementation/11/phpstan.neon b/implementation/11/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/11/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/11/public/favicon.ico b/implementation/11/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/11/public/index.php b/implementation/11/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/11/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/implementation/11/src/Action/Hello.php b/implementation/11/src/Action/Hello.php new file mode 100644 index 0000000..98531eb --- /dev/null +++ b/implementation/11/src/Action/Hello.php @@ -0,0 +1,31 @@ + $name, + 'time' => $clock->now() + ->format('H:i:s'), + ]; + + $content = $renderer->render('hello', $data,); + + $body = $response->getBody(); + $body->write($content); + + return $response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/11/src/Action/Other.php b/implementation/11/src/Action/Other.php new file mode 100644 index 0000000..c42c74b --- /dev/null +++ b/implementation/11/src/Action/Other.php @@ -0,0 +1,24 @@ +response->getBody(); + + $body->write('This works too!'); + + return $this->response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/11/src/Bootstrap.php b/implementation/11/src/Bootstrap.php new file mode 100644 index 0000000..8acaa81 --- /dev/null +++ b/implementation/11/src/Bootstrap.php @@ -0,0 +1,107 @@ +pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +$container = require __DIR__ . '/../config/container.php'; +assert($container instanceof ContainerInterface); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$dispatcher = $container->get(Dispatcher::class); +assert($dispatcher instanceof Dispatcher); + +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + $routeTarget = $routeInfo[1]; + $args = $routeInfo[2]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $args['request'] = $request; + $invoker = $container->get(InvokerInterface::class); + assert($invoker instanceof InvokerInterface); + $response = $invoker->call($routeTarget, $args); + assert($response instanceof ResponseInterface); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody() + ->write('Method not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody() + ->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/11/src/Exception/InternalServerError.php b/implementation/11/src/Exception/InternalServerError.php new file mode 100644 index 0000000..9c3b369 --- /dev/null +++ b/implementation/11/src/Exception/InternalServerError.php @@ -0,0 +1,9 @@ +engine->render($template, $data); + } +} diff --git a/implementation/11/src/Template/Renderer.php b/implementation/11/src/Template/Renderer.php new file mode 100644 index 0000000..de84970 --- /dev/null +++ b/implementation/11/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data): string; +} diff --git a/implementation/11/templates/hello.html b/implementation/11/templates/hello.html new file mode 100644 index 0000000..ca12a59 --- /dev/null +++ b/implementation/11/templates/hello.html @@ -0,0 +1,2 @@ +

Hello {{name}}

+

The time is: {{time}}

\ No newline at end of file From 05f444152efa406e3d9d42eea210110a943c542b Mon Sep 17 00:00:00 2001 From: lubiana Date: Tue, 31 May 2022 17:48:26 +0200 Subject: [PATCH 311/314] simplify chapter 12 --- 12-configuration.md | 216 ++++++++++++-------------------------------- 1 file changed, 59 insertions(+), 157 deletions(-) diff --git a/12-configuration.md b/12-configuration.md index 4b60c19..bb3920d 100644 --- a/12-configuration.md +++ b/12-configuration.md @@ -4,197 +4,99 @@ In the last chapter we added some more definitions to our dependencies.php in that definitions we needed to pass quite a few configuration settings and filesystem strings to the constructors -of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project. +of the classes. This might work for a small projects, but if we are growing we want to source that out to a more +explicit file that holds all the configuration values for our project. -As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. +As this is not a problem unique to our project there are already a some options available. Some projects use +[.env](https://github.com/vlucas/phpdotenv) files, others use +[.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is +[yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex +Readers for many configuration file formats that can be used, take a look at the +[laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example. -As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on. +As I am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am +quite happy that PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at +[this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic +before moving on. -Lets create a 'Settings' class in our './src' Folder: +For the purpose of this Tutorial I will use a simple ValueObject that has all our configuration values as properties. +create a `Configuration.php` class in the `./src` folder: ```php fn (Configuration $c) => simpleDispatcher(require $c->routesFile), + Mustache_Loader_FilesystemLoader::class => fn (Configuration $c) => new Mustache_Loader_FilesystemLoader( + $c->templateDir, + [ + 'extension' => $c->templateExtension, + ] + ), +``` + +Magically this is all we need to do, as the PHP-DI container knows that all constructor parameters of our configuration +class have default values and can create the needed object on its own. + +There is one small problem: If we want to change environment from `dev` to `prod` we would need to update the +configuration class in the src directory. This is something we don't want to do on every deployment. So lets add a file +in our `./config` directory called `settings.php` that returns a Configuration object. ```php fn () => require __DIR__ . '/settings.php', ``` -And write a simple implementation that uses our settings.php to provide our App with the Settingsobject: - +One small oversight to fix is in the registration of our error-handler in the bootstrap-file. There we read the +environment with the getenv-method. Lets change the line: ```php -filePath; - } -} +$environment = getenv('ENVIRONMENT') ?: 'dev'; ``` -If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files -and craft a settings object from them. - -As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another -factory for our Container because everyone should have a Friend :) - +to: ```php -environment; ``` -And a simple implementation that uses our new Settingsprovider to build the container: - -```php -settingsProvider->getSettings(); - $dependencies = require $settings->dependenciesFile; - $dependencies[Settings::class] = fn () => $settings; - $builder->addDefinitions($dependencies); - return $builder->build(); - } -} -``` - -For this to work we need to change our dependencies.php file to just return the array of definitions: -And here we can instantly use the Settings object to create our template engine. - -```php - fn (ResponseFactory $rf) => $rf->createResponse(), - ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(), - Now::class => fn (SystemClockNow $n) => $n, - Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e), - MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]), - ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]), -]; -``` - -Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects: - -```php -... -error_reporting(E_ALL); - -$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php'); -$container = (new SettingsContainerProvider($settingsProvider))->getContainer(); - -$settings = $settingsProvider->getSettings(); - -$whoops = new Run; -if ($settings->environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (Throwable $e): void { - error_log('Error: ' . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); -} -$whoops->register(); -... -``` - -Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter. +Check if everything still works, run your code quality checks and commit the changes before moving on the next chapter. +You might notice that phpstan throws an error as there is a documented violation missing. You can either regenerate the +baseline, or simply remove that line from the `phpstan-baseline.neon` file. [<< previous](11-templating.md) | [next >>](13-refactoring.md) From 0cd0b9def067b8b539e0cea380c49a567d2386b3 Mon Sep 17 00:00:00 2001 From: lubiana Date: Tue, 31 May 2022 17:49:17 +0200 Subject: [PATCH 312/314] add chapter 12 solutions --- implementation/12/composer.json | 51 + implementation/12/composer.lock | 1516 +++++++++++++++++ implementation/12/config/container.php | 38 + implementation/12/config/routes.php | 12 + implementation/12/config/settings.php | 5 + implementation/12/ecs.php | 89 + implementation/12/phpstan-baseline.neon | 6 + implementation/12/phpstan.neon | 8 + implementation/12/public/favicon.ico | Bin 0 -> 15086 bytes implementation/12/public/index.php | 3 + implementation/12/rector.php | 12 + implementation/12/src/Action/Hello.php | 31 + implementation/12/src/Action/Other.php | 24 + implementation/12/src/Bootstrap.php | 109 ++ implementation/12/src/Configuration.php | 14 + .../12/src/Exception/InternalServerError.php | 9 + .../12/src/Exception/MethodNotAllowed.php | 9 + implementation/12/src/Exception/NotFound.php | 9 + implementation/12/src/Service/Time/Clock.php | 10 + .../12/src/Service/Time/SystemClock.php | 13 + .../12/src/Template/MustacheRenderer.php | 17 + implementation/12/src/Template/Renderer.php | 11 + implementation/12/templates/hello.html | 2 + 23 files changed, 1998 insertions(+) create mode 100644 implementation/12/composer.json create mode 100644 implementation/12/composer.lock create mode 100644 implementation/12/config/container.php create mode 100644 implementation/12/config/routes.php create mode 100644 implementation/12/config/settings.php create mode 100644 implementation/12/ecs.php create mode 100644 implementation/12/phpstan-baseline.neon create mode 100644 implementation/12/phpstan.neon create mode 100644 implementation/12/public/favicon.ico create mode 100644 implementation/12/public/index.php create mode 100644 implementation/12/rector.php create mode 100644 implementation/12/src/Action/Hello.php create mode 100644 implementation/12/src/Action/Other.php create mode 100644 implementation/12/src/Bootstrap.php create mode 100644 implementation/12/src/Configuration.php create mode 100644 implementation/12/src/Exception/InternalServerError.php create mode 100644 implementation/12/src/Exception/MethodNotAllowed.php create mode 100644 implementation/12/src/Exception/NotFound.php create mode 100644 implementation/12/src/Service/Time/Clock.php create mode 100644 implementation/12/src/Service/Time/SystemClock.php create mode 100644 implementation/12/src/Template/MustacheRenderer.php create mode 100644 implementation/12/src/Template/Renderer.php create mode 100644 implementation/12/templates/hello.html diff --git a/implementation/12/composer.json b/implementation/12/composer.json new file mode 100644 index 0000000..c0fe924 --- /dev/null +++ b/implementation/12/composer.json @@ -0,0 +1,51 @@ +{ + "name": "lubian/no-framework", + "autoload": { + "psr-4": { + "Lubian\\NoFramework\\": "src/" + } + }, + "authors": [ + { + "name": "example", + "email": "test@example.com" + } + ], + "require": { + "php": ">=8.1", + "filp/whoops": "^2.14", + "laminas/laminas-diactoros": "^2.11", + "nikic/fast-route": "^1.3", + "psr/http-server-handler": "^1.0", + "psr/container": "^1.0", + "php-di/php-di": "^6.4", + "mustache/mustache": "^2.14" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "symfony/var-dumper": "^6.0", + "slevomat/coding-standard": "^7.2", + "symplify/easy-coding-standard": "^10.2", + "rector/rector": "^0.12.23", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "php -S 0.0.0.0:1235 -t public" + ], + "phpstan": "./vendor/bin/phpstan analyze", + "baseline": "./vendor/bin/phpstan analyze --generate-baseline", + "check": "./vendor/bin/ecs", + "fix": "./vendor/bin/ecs --fix", + "rector": "./vendor/bin/rector process" + } +} diff --git a/implementation/12/composer.lock b/implementation/12/composer.lock new file mode 100644 index 0000000..c3de49e --- /dev/null +++ b/implementation/12/composer.lock @@ -0,0 +1,1516 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "ea5b586e05e6b1d75bded814662047b4", + "packages": [ + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", + "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-05-17T10:57:52+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "pestphp/pest": "^1.18", + "phpstan/phpstan": "^0.12.98", + "symfony/var-dumper": "^5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2022-05-16T17:09:47+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", + "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-12-13T09:22:56+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.4.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "shasum": "" + }, + "require": { + "laravel/serializable-closure": "^1.0", + "php": ">=7.4.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.10", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.11.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^9.5" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.4.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2022-04-09T16:46:38+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-handler/issues", + "source": "https://github.com/php-fig/http-server-handler/tree/master" + }, + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" + }, + "time": "2020-12-13T13:06:13+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "981cc368a216c988e862a75e526b6076987d1b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + }, + "time": "2022-05-05T11:32:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.6.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-05-10T06:54:21+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.6.3" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" + }, + "time": "2022-05-04T15:20:40+00:00" + }, + { + "name": "rector/rector", + "version": "0.12.23", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "690b31768b322db886b35845f8452025eba2cacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", + "reference": "690b31768b322db886b35845f8452025eba2cacb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.6" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.2", + "rector/rector-cakephp": "*", + "rector/rector-doctrine": "*", + "rector/rector-laravel": "*", + "rector/rector-nette": "*", + "rector/rector-phpoffice": "*", + "rector/rector-phpunit": "*", + "rector/rector-prefixed": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.12.23" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-01T15:50:16+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-06T10:58:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-26T13:22:23+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "10.2.6", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.0", + "squizlabs/php_codesniffer": "<3.6" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.5-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prefixed scoped version of ECS package", + "support": { + "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2022-05-17T07:11:50+00:00" + }, + { + "name": "thecodingmachine/phpstan-strict-rules", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "phpstan/phpstan": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^7.1" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "phpstan": { + "includes": [ + "phpstan-strict-rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TheCodingMachine\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Négrier", + "email": "d.negrier@thecodingmachine.com" + } + ], + "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", + "support": { + "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", + "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" + }, + "time": "2021-11-08T09:10:49+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/implementation/12/config/container.php b/implementation/12/config/container.php new file mode 100644 index 0000000..1f76fa2 --- /dev/null +++ b/implementation/12/config/container.php @@ -0,0 +1,38 @@ +addDefinitions( + [ + ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(), + ResponseInterface::class => fn () => new Response, + Clock::class => fn () => new SystemClock, + Renderer::class => fn (MustacheRenderer $me) => $me, + Dispatcher::class => fn (Configuration $c) => simpleDispatcher(require $c->routesFile), + Mustache_Loader_FilesystemLoader::class => fn (Configuration $c) => new Mustache_Loader_FilesystemLoader( + $c->templateDir, + [ + 'extension' => $c->templateExtension, + ] + ), + Mustache_Engine::class => fn (Mustache_Loader_FilesystemLoader $loader) => new Mustache_Engine([ + 'loader' => $loader, + ]), + Configuration::class => fn () => require __DIR__ . '/settings.php', + ] +); + +return $builder->build(); diff --git a/implementation/12/config/routes.php b/implementation/12/config/routes.php new file mode 100644 index 0000000..b68712f --- /dev/null +++ b/implementation/12/config/routes.php @@ -0,0 +1,12 @@ +addRoute('GET', '/hello[/{name}]', Hello::class); + $r->addRoute('GET', '/other', [Other::class, 'handle']); + $r->addRoute('GET', '/', fn (ResponseInterface $r) => $r->withHeader('Location', '/hello') ->withStatus(302)); +}; diff --git a/implementation/12/config/settings.php b/implementation/12/config/settings.php new file mode 100644 index 0000000..1862e1f --- /dev/null +++ b/implementation/12/config/settings.php @@ -0,0 +1,5 @@ +parallel(); + $config->paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + $config->skip([BlankLineAfterOpeningTagFixer::class, OrderedImportsFixer::class, NewWithBracesFixer::class]); + + $config->sets([ + SetList::PSR_12, + SetList::STRICT, + SetList::ARRAY, + SetList::SPACES, + SetList::DOCBLOCK, + SetList::CLEAN_CODE, + SetList::COMMON, + SetList::COMMENTS, + SetList::NAMESPACES, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + // force visibility declaration on class constants + $config->ruleWithConfiguration(ClassConstantVisibilitySniff::class, [ + 'fixable' => true, + ]); + + // sort all use statements + $config->rules([ + AlphabeticallySortedUsesSniff::class, + DisallowGroupUseSniff::class, + MultipleUsesPerLineSniff::class, + NamespaceSpacingSniff::class, + ]); + + // import all namespaces, and event php core functions and classes + $config->ruleWithConfiguration( + ReferenceUsedNamesOnlySniff::class, + [ + 'allowFallbackGlobalConstants' => false, + 'allowFallbackGlobalFunctions' => false, + 'allowFullyQualifiedGlobalClasses' => false, + 'allowFullyQualifiedGlobalConstants' => false, + 'allowFullyQualifiedGlobalFunctions' => false, + 'allowFullyQualifiedNameForCollidingClasses' => true, + 'allowFullyQualifiedNameForCollidingConstants' => true, + 'allowFullyQualifiedNameForCollidingFunctions' => true, + 'searchAnnotations' => true, + ] + ); + + // define newlines between use statements + $config->ruleWithConfiguration(UseSpacingSniff::class, [ + 'linesCountBeforeFirstUse' => 1, + 'linesCountBetweenUseTypes' => 1, + 'linesCountAfterLastUse' => 1, + ]); + + // strict types declaration should be on same line as opening tag + $config->ruleWithConfiguration(DeclareStrictTypesSniff::class, [ + 'declareOnFirstLine' => true, + 'spacesCountAroundEqualsSign' => 0, + ]); + + // disallow ?Foo typehint in favor of Foo|null + $config->ruleWithConfiguration(UnionTypeHintFormatSniff::class, [ + 'withSpaces' => 'no', + 'shortNullable' => 'no', + 'nullPosition' => 'last', + ]); + + // Remove useless parentheses in new statements + $config->rule(NewWithoutParenthesesSniff::class); +}; diff --git a/implementation/12/phpstan-baseline.neon b/implementation/12/phpstan-baseline.neon new file mode 100644 index 0000000..38383b9 --- /dev/null +++ b/implementation/12/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Bootstrap.php diff --git a/implementation/12/phpstan.neon b/implementation/12/phpstan.neon new file mode 100644 index 0000000..2eac45a --- /dev/null +++ b/implementation/12/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - config \ No newline at end of file diff --git a/implementation/12/public/favicon.ico b/implementation/12/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09499b8b3b3201e0f50088e3ac42e167778d1153 GIT binary patch literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< literal 0 HcmV?d00001 diff --git a/implementation/12/public/index.php b/implementation/12/public/index.php new file mode 100644 index 0000000..970d132 --- /dev/null +++ b/implementation/12/public/index.php @@ -0,0 +1,3 @@ +paths([__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']); + + $rectorConfig->importNames(); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); +}; diff --git a/implementation/12/src/Action/Hello.php b/implementation/12/src/Action/Hello.php new file mode 100644 index 0000000..98531eb --- /dev/null +++ b/implementation/12/src/Action/Hello.php @@ -0,0 +1,31 @@ + $name, + 'time' => $clock->now() + ->format('H:i:s'), + ]; + + $content = $renderer->render('hello', $data,); + + $body = $response->getBody(); + $body->write($content); + + return $response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/12/src/Action/Other.php b/implementation/12/src/Action/Other.php new file mode 100644 index 0000000..c42c74b --- /dev/null +++ b/implementation/12/src/Action/Other.php @@ -0,0 +1,24 @@ +response->getBody(); + + $body->write('This works too!'); + + return $this->response->withBody($body) + ->withStatus(200); + } +} diff --git a/implementation/12/src/Bootstrap.php b/implementation/12/src/Bootstrap.php new file mode 100644 index 0000000..152edf4 --- /dev/null +++ b/implementation/12/src/Bootstrap.php @@ -0,0 +1,109 @@ +environment; + +error_reporting(E_ALL); + +$whoops = new Run; + +if ($environment === 'dev') { + $whoops->pushHandler(new PrettyPageHandler); +} else { + $whoops->pushHandler(function (Throwable $t) { + error_log('ERROR: ' . $t->getMessage(), $t->getCode()); + echo 'Oooopsie'; + }); +} + +$whoops->register(); + +$container = require __DIR__ . '/../config/container.php'; +assert($container instanceof ContainerInterface); + +$request = $container->get(ServerRequestInterface::class); +assert($request instanceof ServerRequestInterface); + +$dispatcher = $container->get(Dispatcher::class); +assert($dispatcher instanceof Dispatcher); + +$routeInfo = $dispatcher->dispatch($request->getMethod(), $request->getUri() ->getPath(),); + +try { + switch ($routeInfo[0]) { + case Dispatcher::FOUND: + $routeTarget = $routeInfo[1]; + $args = $routeInfo[2]; + foreach ($routeInfo[2] as $attributeName => $attributeValue) { + $request = $request->withAttribute($attributeName, $attributeValue); + } + $args['request'] = $request; + $invoker = $container->get(InvokerInterface::class); + assert($invoker instanceof InvokerInterface); + $response = $invoker->call($routeTarget, $args); + assert($response instanceof ResponseInterface); + break; + case Dispatcher::METHOD_NOT_ALLOWED: + throw new MethodNotAllowed; + case Dispatcher::NOT_FOUND: + default: + throw new NotFound; + } +} catch (MethodNotAllowed) { + $response = (new Response)->withStatus(405); + $response->getBody() + ->write('Method not Allowed'); +} catch (NotFound) { + $response = (new Response)->withStatus(404); + $response->getBody() + ->write('Not Found'); +} catch (Throwable $t) { + throw new InternalServerError($t->getMessage(), $t->getCode(), $t); +} + +foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } +} + +$statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() +); +header($statusLine, true, $response->getStatusCode()); + +echo $response->getBody(); diff --git a/implementation/12/src/Configuration.php b/implementation/12/src/Configuration.php new file mode 100644 index 0000000..7483fe8 --- /dev/null +++ b/implementation/12/src/Configuration.php @@ -0,0 +1,14 @@ +engine->render($template, $data); + } +} diff --git a/implementation/12/src/Template/Renderer.php b/implementation/12/src/Template/Renderer.php new file mode 100644 index 0000000..de84970 --- /dev/null +++ b/implementation/12/src/Template/Renderer.php @@ -0,0 +1,11 @@ + $data + */ + public function render(string $template, array $data): string; +} diff --git a/implementation/12/templates/hello.html b/implementation/12/templates/hello.html new file mode 100644 index 0000000..ca12a59 --- /dev/null +++ b/implementation/12/templates/hello.html @@ -0,0 +1,2 @@ +

Hello {{name}}

+

The time is: {{time}}

\ No newline at end of file From 81c47d94ca5cea3ddce44e04c6702a3112d455f9 Mon Sep 17 00:00:00 2001 From: lubiana Date: Tue, 31 May 2022 18:05:46 +0200 Subject: [PATCH 313/314] fix typos in chapters 15 to 17 --- 15-adding-content.md | 24 ++++++++++++------------ 16-data-repository.md | 24 ++++++++++++------------ 17-performance.md | 14 +++++++------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/15-adding-content.md b/15-adding-content.md index 0e85af0..753c395 100644 --- a/15-adding-content.md +++ b/15-adding-content.md @@ -2,7 +2,7 @@ ### Adding Content -By now we did not really display anything but some examples to in our application and it is now time to make our app +By now we did not really display anything but some examples to in our application, and it is now time to make our app display some content. For example we could our app be able to display the Markdown files used in this tutorial as nicely rendered HTML Pages that can be viewed in the browser instead of the editor you are using. @@ -15,7 +15,7 @@ can use whatever you like. After installing Parsedown lets write a Markdownparser interface and an implementation using parsedown. -We only need one function that receives a string of Markdown and returns the HTML represantion (as a string as well). +We only need one function that receives a string of Markdown and returns the HTML representation (as a string as well). @@ -30,8 +30,8 @@ interface MarkdownParser } ``` -By the namespace you will already have guessed that I called placed in interface in a file calles MarkdownParser.php in -the src/Template folder. Lets put our Parsedown implementation right next to it in a file called ParsedownParser.php +By the namespace you will already have guessed that I placed in interface in a file calles MarkdownParser.php in +the src/Template folder. Let's put our Parsedown implementation right next to it in a file called ParsedownParser.php ```php addRoute('GET', '/page', [Page::class, 'list']); $r->addRoute('GET', '/page/{page}', [Page::class, 'show']); ``` -Here is my Implementation. If have added a little regex replacement in the show method that replaces the links to the +Here is my Implementation. I have added a little regex replacement in the show method that replaces the links to the next and previous chapter so that it works with our routing configuration. `src/Action/Page.php` @@ -244,9 +244,9 @@ class Page You can now navigate your Browser to [localhost:1235/page][http://localhost:1235/page] and try out if everything works. Of course this code is far from looking good. We heavily rely on the pages being files in the filesystem, and the action -should never be aware of the filesystem in the first place, also we have a lot of string replacements and other repetetive -code in the file. And phpstan is gonna scream at us a lot, but if we rewrite the code to satisfy all the checks we would -add even more lines to that simple class, so lets move on to the next chapter where we move all the logic to seperate +should never be aware of the filesystem in the first place, also we have a lot of string replacements and other repetitive +code in the file. And phpstan is going to scream at us a lot, but if we rewrite the code to satisfy all the checks we would +add even more lines to that simple class, so lets move on to the next chapter where we move all the logic to separate classes following our holy SOLID principles :) diff --git a/16-data-repository.md b/16-data-repository.md index d9a3218..1677f0a 100644 --- a/16-data-repository.md +++ b/16-data-repository.md @@ -2,32 +2,32 @@ ## Data Repository -At the end of the last chapter I mentioned being unhappy with our Pages action, because there is to much stuff happening -there. We are firstly receiving some Arguments, then we are using those to query the filesytem for the given page, +At the end of the last chapter I mentioned being unhappy with our Pages action, because there is too much stuff happening +there. We are firstly receiving some Arguments, then we are using those to query the filesystem for the given page, loading the specific file from the filesystem, rendering the markdown, passing the markdown to the template renderer, adding the resulting html to the response and then returning the response. -In order to make our pageaction independent from the filesystem and move the code that is responsible for reading the +In order to make our page-action independent of the filesystem and move the code that is responsible for reading the files to a better place I want to introduce the [Repository Pattern](https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html). -I want to start by creating a class that represents the Data that is included in a page so that. For now I can spot +I want to start by creating a class that represents the Data that is included in a page so that. For now, I can spot three -distrinct attributes. +distinct attributes. -* the ID (or chapternumber) +* the ID (or chapter-number) * the title (or name) * the content -Currently all those properties are always available, but we might later be able to create new pages and store them, but +Currently, all those properties are always available, but we might later be able to create new pages and store them, but at that point in time we are not yet aware of the new available ID, so we should leave that property nullable. This allows -us to create an object without an id and let the code that actually saves the object to a persistant store define a +us to create an object without an id and let the code that actually saves the object to a persistent store define a valid id on saving. -Lets create an new Namespace called `Model` and put a `MarkdownPage.php` class in there: +Let's create an new Namespace called `Model` and put a `MarkdownPage.php` class in there: ```php Date: Tue, 8 Jul 2025 22:30:04 +0200 Subject: [PATCH 314/314] start rework for 2025 --- 01-front-controller.md | 54 +- 02-composer.md | 27 +- 03-error-handler.md | 110 +- app/public/favicon.ico | Bin 15086 -> 0 bytes implementation/01/public/index.php | 5 + implementation/01/src/Bootstrap.php | 5 + implementation/02/composer.json | 19 + implementation/02/public/index.php | 5 + implementation/02/src/Bootstrap.php | 7 + implementation/03/composer.json | 10 +- implementation/03/public/favicon.ico | Bin 15086 -> 0 bytes implementation/03/public/index.php | 6 +- implementation/03/src/Bootstrap.php | 39 +- implementation/12/composer.lock | 1516 -------------------------- 14 files changed, 214 insertions(+), 1589 deletions(-) delete mode 100644 app/public/favicon.ico create mode 100644 implementation/01/public/index.php create mode 100644 implementation/01/src/Bootstrap.php create mode 100644 implementation/02/composer.json create mode 100644 implementation/02/public/index.php create mode 100644 implementation/02/src/Bootstrap.php delete mode 100644 implementation/03/public/favicon.ico delete mode 100644 implementation/12/composer.lock diff --git a/01-front-controller.md b/01-front-controller.md index 53d17ef..ed2c184 100644 --- a/01-front-controller.md +++ b/01-front-controller.md @@ -2,29 +2,45 @@ ### Front Controller -A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your application. +A [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern) is a single point of entry for your +application. -To start, create an empty directory for your project. You also need an entry point where all requests will go to. This means you will have to create an `index.php` file. +To start, create an empty directory for your project. You also need an entry point where all requests will go to. This +means you will have to create an `index.php` file. -A common way to do this is to just put the `index.php` in the root folder of the projects. This is also how some frameworks do it. Let me explain why you should not do this. +A common way to do this is to just put the `index.php` in the root folder of the projects. Let me explain why you should not do this. -The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders where your application files are. +The `index.php` is the starting point, so it has to be inside the web server directory. This means that the web server +has access to all subdirectories. If you set things up properly, you can still prevent it from accessing your subfolders +where your application files are. -But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. +But sometimes things don't go according to plan. And if something goes wrong and your files are set up as above, your +whole application source code could be exposed to visitors. I won't have to explain why this is not a good thing. -So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` folder for your application, also in the project root folder. +So instead of doing that, create a folder in your project folder called `public`. This is a good time to create an `src` +folder for your application, also in the project root folder. -Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so put just the following code in there: +Inside the `public` folder you can now create your `index.php`. Remember that you don't want to expose anything here, so +put just the following code in there: ```php -getCode()) { + E_ERROR, E_USER_ERROR => 'Fatal Error', + E_WARNING, E_USER_WARNING => 'Warning', + E_NOTICE, E_USER_NOTICE => 'Notice', + default => 'Unknown Error' + }; + + echo <<{$errorType} +

{$t->getMessage()}

+
{$t->getTraceAsString()}
+ HTML; +}); + +set_error_handler( + function (int $errno, string $errstr, string $errfile, int $errline) { + throw new ErrorException( + message: $errstr, + code: $errno, + severity: $errno, + filename: $errfile, + line: $errline + ); + } +); + +echo 'Hello world!'; +``` + +You can then replace `echo 'Hello world!';` with `trigger_error('This is a test error');` +or `throw new Exception('This is a test exception');` and open it in your browser to see if the error handling works. + + +During development there are some other nice features to add. For example a quick link to open your Editor on the file the Error occured. So the first package for your application will take care of that. I like [filp/whoops](https://github.com/filp/whoops), so I will show how you can install that package for your project. If you prefer another package, feel free to install that one. This is the beauty of programming without a framework, you have total control over your project. -An alternative package would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) +Some alternatives would be: [PHP-Error](https://github.com/JosephLenton/PHP-Error) or [Tracy](https://tracy.nette.org/en/) -To install a new package, open up your `composer.json` and add the package to the require part. It should now look -like this: - -```php -"require": { - "php": ">=8.1.0", - "filp/whoops": "^2.14" -}, -``` - -Now run `composer update` in your console, and it will be installed. - -Another way to install packages is to simply type "composer require filp/whoops" into your terminal at the project root, -i that case composer automatically installs the package and updates your composer.json-file. +To install that package into your project simply type `composer require filp/whoops` into your terminal at the project root, +now composer automatically looks for a version of that package compatible with the rest of your project and your php +version. But you can't use it yet. PHP won't know where to find the files for the classes. For this you will need an autoloader, ideally a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader. Composer already takes care of this for you, so you @@ -36,7 +75,7 @@ only have to add a `require __DIR__ . '/../vendor/autoload.php';` to your `Boots can help someone to gain access to your system. Always show a user-friendly error page instead and send an email to yourself, write to a log or something similar. So only you can see the errors in the production environment. -For development that does not make sense though -- you want a nice error page. The solution is to have an environment +For development that does not make sense, though -- you want a nice error page. The solution is to have an environment switch in your code. We use the getenv() function here to check the environment and define the 'dev' env as standard in case no environment has been set. @@ -44,36 +83,51 @@ Then after the error handler registration, throw an `Exception` to test if every Your `Bootstrap.php` should now look similar to this: ```php -pushHandler( + new CallbackHandler( + function (Throwable $e) use ($environment) { + if ($environment !== 'dev') { + http_response_code(500); + echo 'Whoops'; + } + error_log(<<getMessage()} + {$e->getTraceAsString()} + TXT + ); + } + ) +); + if ($environment === 'dev') { $whoops->pushHandler(new PrettyPageHandler); -} else { - $whoops->pushHandler(function (\Throwable $e) { - error_log("Error: " . $e->getMessage(), $e->getCode()); - echo 'An Error happened'; - }); } $whoops->register(); -throw new \Exception("Ooooopsie"); +throw new \Exception('Hello world'); ``` You should now see a error page with the line highlighted where you throw the exception. If not, go back and debug until you get it working. Now would also be a good time for another commit. +**Side-note:** Here we use `getenv()` to read a Variable from the Environment and fallback to using 'dev' if it is not set. That is a bad default, and in a production app you should always default to the strictest mode. +There are also good libraries that help with managing that in a better fashion. You can take a look at [phpdotenv](https://github.com/vlucas/phpdotenv) or [symfony/dotenv](https://github.com/symfony/dotenv) and maybe implement them. This tutorial however skips this step. [<< previous](02-composer.md) | [next >>](04-development-helpers.md) diff --git a/app/public/favicon.ico b/app/public/favicon.ico deleted file mode 100644 index 09499b8b3b3201e0f50088e3ac42e167778d1153..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/01/public/index.php b/implementation/01/public/index.php new file mode 100644 index 0000000..64740cd --- /dev/null +++ b/implementation/01/public/index.php @@ -0,0 +1,5 @@ +=8.1", - "filp/whoops": "^2.14" + "php": "^8.4", + "filp/whoops": "^2.18" } } diff --git a/implementation/03/public/favicon.ico b/implementation/03/public/favicon.ico deleted file mode 100644 index 09499b8b3b3201e0f50088e3ac42e167778d1153..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHOcUY8H)*m&AF>aFG^yX?}Z|L zqe(VZlifs9o|uiBjr_iIUzqv87>#+p=iB|md7jICXXbt9o^$Rw=bm$Z2Oe(*Zx&BU ziAVDSUh`reZ#|F4Td-jA{hxWfkLa^M)6BjXKE~sT>4s%=4?V(jrolevUW+ECM}Hql z;E@C#N#KzL{&NygnZJ03-g3p)EZ1z%vv_;GujSk8W3AV2jHPQI6UDVU`YV-Reh_sp z@}8No`L9b}Fj%RiXt+{Y!&Gst7CozKxO&Z^?JJb$Jm`7$Jw5w44JA{>HP$pN>3XHn zD&<-Cdsb`ls@aaJTfAfWrkBcn-ES9ryQ0L~1z7?MWDEF+H!=9hOI`ge@!~}E(1|qt zrM_tOV?^&;PO4i{#R99daTaDbQ*AB(8LYGIcBr=I55Ah}*PJ$~^zfC}Tbe4adt&<2 z)8E;y-;^A0Vg8Q_UynOwB9A*!rY6^|)^2c_e%Hi1T5|zkd7a2dORKdi!0Xl_NeqTF zV{xW14eu6-(G=*8t}suu2fLw0XpbNrt?$g0*BQ!w%UEH}YxLgFZvDpgL@WLW^c&-2 zxrsQK5sgdbQq+i?QEG33QU?>{S{Wc>pMtBEIoue@OaRe$ zMB~k6PfxwyOmXd2;kIoTQfw^7GaYR)kRFYbJ5q@LIGmuFT{i`Kpex)99U&grOFZm! zvV`6GO{MI2%#_wGaaG+ioMLV~9&4nBuJ{n{*;6}HaY!1AE4#8#=kH4N8>8IO6ctXU zD6lbti;C(={Z(t8n@Y>n>u57moGX^#a(Nav-YrZ=xRK%2tqWJYBD;@t>d#45{E@bp zKrnfc-k_K0KU$E2(4n6E8AlOzhZJamY)Pa9Uat1`r}|uB0i|kM^{1!nuER26XS>O zC=vEWc#|G^q0&>p(f{(3FFjT2V%OghO=Tv1H) z%4nZL|7S%1n=)GNo}x zG(b~`51J$VFjkz7y)gmkj}N2~!11s@$_MX62vH?;;OL(>_tm-i4%UM`5hApbjz#F{ zkd5o3%*UB~{s8I!4!a4xd06^m_wYMXjR8(75y|`d*h~D172Db^O~NesoYtT-H@JsC-l#87G&4km}-Ktdq#;Lq_;Oa8=3W%F*e zg)8Th9SoLv+M|~IQi{0&()b3b^RUJ);-SLHY?|IY4E+f^G`>7)^dB!^#99+qIBxVTgJ!ThzE){U#5;NB&6ydt9n1oJN0Bm>;axY>1H2 z&uk&p*6QYk5-HB?NF)2OM1M*IPUR)xSav+dq%oW>4W}_4m>val`_SO;h5#LHIiX1 zt+zdDyzJ2AW6$kDE$LB}s|6~Z%^#ZnG#6J~X{wOZ&-8yt5(f_r%^sC`i=SjX$mD;C zd=vApVmm7gilg!F&NSMCWE>|wIyz|!(|HJ*^hspTkv?>`3AwYaJ;)8M0WR3>>xf1l zfn1Lm4^wU7_sBop-2?BpRLkjS{)f?+Vq^J@{t6|%sq~*M%D{oNXhfTt;7~>^&K0HO zOhGD6k{)q9kRLxHAzPSc&&((J(;oQB_h2ub&uxLOXz_POv&ezdqdG5}U)jR%p+CdZ z3m>%CPotmh!64BWX=3~_=}`v7ED|rxZ9k?9Y`_M^E*Q&B!Uf{2$f)=4qa-e7l%libls`?804X%O0>&eI0f?v`?U{$2D7z45os-P7o2 zd%$=&S&)W~*bp>?h#(eNqb}HoXqHl}mH~FoT`H4O+?9>Pb(N@>q~k%9}QK$#{^U_h3l8laADfR@p%WZ+#GxMuO0uy*_+5DN2!f&6y5^o=Tyua;N z^iS}R1?Ep#Y{%@j+}8s~bCWsV*!P`rAvi`nb{6Eqa-9k~Xg*(-g;QkL$0#OZwlGZg zJdz%TLp1kIo^3s}7d^yFUx5^E+ImRy5uqAs=WWMC? zS>of1Px?CVNk5}eHW(kh$&uI{?$6QB_NUOx1yun;4DTw1{bo%hxVmF^mIN)C8R#o6 zz=`raoG6xJ-_CsO+mVamtOOjSIIpKDAI;Kqj5Smt+*SY&9ewOdN<>vc92(P-(Up;i zMn5Owfsa&EUFORZ?xtJzULx|trvv-tbjs+xdw!32IYqv|($5nYN~IhRN5}@Gj&?|O za)iCA8mu>{BFxqv0emYsZ`DDDuOHs+XhD*v5F%r9jFjYJgkr$bxLDY!s$-zG3fV!y zFjra&e=`e&+6drIyaZX=pe<93M)FTN)ire{j9xaT z^1=L1kR19l&LxMMmpIlJCjF44f*Gkd02nrYuKb_M7iUK0DMG>@1Vr&-l1P<1pz*f`u6u z+nsN!1J{T&_!2sDb(>umft27OH0(01|+gKo@_wG5_)B9gvo;iUl z)%hGfOwNTOPn_!SMNUK%>Y~Hsd+=Vxgg!AkWi(Hn+2_O3IQZ%tLTF$DXH9L?r>Eg; zYXd|^X2gR!f-P;iYo`B&QBgQiUx5hPBLTe+u@)eZZw)66Eu;`X-I4^Pm>GaiHI2LJ zmc5S@INtc;;(2_yn_`GbT`Bc(N31pZ`8p!uFic_#RI|IF$Nob)M zgJRWhDDP(_clqm2yxDSH^42pv_u=VN_;RF=;(-h<7iRutZ%i;+qXM~eN=DyY^k8!{ZvW#t4l&)M zb9P7)&*==~W2&7#kp4;ev-p~w-7LO1$Knf$laADsqbXB@W2FB_C{~?_kyxzCa-Ltu zNFAY`h%we5r<#_S#@iIb*a|kNyq9QYPCnZf4l0|^Q=Mw5!LrrbsUDs;zPo-6-~R1= zw5KN{+etvVsT&%|&x}#dBGdZ^)1M7?CZv)4nSFEdDvOO64|IN?C4Np&tT!#^VR;+L zULGS+e9=pLQ0n0bp@znIkoLBJdTVI>Nb>$U%G4MoKCY+@5+cXd{tnf@PU|dL^|;O2 zjhQv6sdsLD^>_UI%?(^1JA{k%r8q~i0OvoMy|OWBAHT&1^P&9|pGqC<gGe6#NzHf?TZdS$pm@h`+*L2f|6j~9lA@Z*iIIX-^)=qkQCF*-R$@Igb# zeR*Ip2Fu+_$o{WT%p^Ng7!NGg=JJL7}|pPoO5YnLx!sI?8B9391H-FxsJqwOAh!S*W0)rsPvGW=tt zAJ-4=$F&1p_`G*7KHcAnk2{<3QF{YEY^}uylyh8duEHhqpGS-H&=3_i9%W|!G08bb zZ@Iz?6I4zAYof5`8M0vms&5LYwkof$O}%T&8*k0ozHphMnS!#5qpDhor?&2mSX)~J z=;j$?ql+7v z=*p6!JtGZ`39;kFf&M=w+uL6k>F5p+O<{V=6qcyWTO{|5Q|bC6*PCB|Yo>|9+qQwa zy4P7A(4QKGJP#L?B_!Z%Uk}H}=e=$CxV;v~OR^DXVg41>EkewwmTj)M#)+@A*4;vR zosepfkzVf z|B(Q=7mxRc=kXciGr;4`zRkPMqj}aR)N(}Cnps0Ujfb3h?EV=;#P>aBP8#(2eP{K_ z?|*B)`}vT``w1;hoV=9HL-c;ur##+wG(QU-Z=7aznvwV{iHhc-`4clIxIZpG)~B_A zh6W9JpBD9UJ$gNoz#oyoo6o%X_?87rU({K;`mfr{6klEQ`r`ZcfxPnAvrj0$vhb-@ zuPvSZ*v#jCGk()uuJAnNCaM%et88DU@XS4Eo9U^cshVzY-H_{Qe=$Q~^>GT{_-cgy z_CCU31zJlL9+y3{Y5ua=f{m)m@fKzoN!I+Ha0A_uz-=16o?EtLQk1vA)pD`P!}kPc#yqev4yV zO8h4fmY!SSXhr&9j2v4N!fmJHh4)*(IxLuaWOBJYd0#@(@m` z;7V0Kw+AdwCp=FyvA&iM!}|KDCfyV&B0MsMlL6OPK$tx1i|8i2s4dis!%#b^uGZx1 zgnGh&IsAIEhu}f$Q@)R@)jPPF0jup%&s96EKgWys@0rAfnyB8!>U|up&f&SygkLcl zChB7h1Kme`C9FS*;kV7A3H+J$05I(Ne*8D5B;ozq37ntRNqdqb{w21v?hDk@JIv}` zRsLRFec%$+iq4c}bM=5@)Sq*h@B)U3v%Ullho$-)hl4XLJRuaVWDjf~ILwb|knr`# z1?xBcO8;7@4*q_9vApiXYKp8z$nZS!ON1?nspgi&VKMzXvLW^s;UvSq$R1g}bC~r< zh$A@~7=G7JI+Gm`fbz&tw3DA`BCM!}aK~&*15oc<5Zl|SuRT;d{h*Qh1n#OkF`qO_ zxCz?>Ru`-Z6rwsR4DN&rX8HM}Ff0Tel4Kkz&c&{{81fDA=txgQZAv0`goVP-*bLcz zekcg=LlgBhl{@o6{dX;o^In-XZGEZ*eb`(+W&KRj#R-h+1YzJUkpbNLOb+c-t4X)F zg~W?=EhPbA*0yNK&PK46E!EXYUP&<=zEqzfhA*vOpBRe_FE7M7Igk&wCaYiWd-%R+lgy@K{v4fQG9k%{{!XTJz z(!7L7BOE`HaFjaYgXCW^Rrc(!2Q)BiY11vj;Zr!b%zcqgkZqK|b7ftOm|-h%;oftp3gUh175kJLxA}y)P~hg>Lr0 zcx!5YFH~3m+-0-c582KFloE~*sHb~*(~GbDDa70&YoM|5$G=~>j2q`qV(ULhZz}u8DVUAjq2F^O4oTA{;C$!h4;2aJj7s7hCFZp{W{Y>&kJWVkZXkB;!pn;oqhT1nl>uC*HbT^Ja%JrQ1JKI#^+yt*+O}(Z;ZL=jjBK&G|;||HPzum zs&T(hexg4;{+A$qgLk5A?4${f&iP4B&PB;i&ZRUek{ldLqb#kYqV0NK7Hc-FQ-0}< zX|~SR_&qPB7v{|*EXz>hV0(Ztn~T1N#;t`>F$YSbqK=1|^HcSfuD)j-{rCL#!`!3r z;=H+<3zxs3wPfYv^JY9YXYpfmo_U%#WvFXJxX%-MD;1wgv$IkW+gf;0 zzGvg6s``?Q{I=+zPbt3`rt{~yY-c;`GLidb%CGKpQI1&YA^4i|S?er2i}t;7!Qbtp zGh-+9$HeGsNoCI;BJ7;{AFq3AY+bTD(!aT$`m7I7+}BHI%j*2_y_a&EQOadmKGPm1{8zGt$&pMOvws&m z^0}C~%)u1N#{c{LNS+uJkNuovW#O>P->bDFHh7}1k77`k4|P&LxF^sJP1JYD`aW6Q zKlS;;6f3av>tK59HOlk89-uzW;oMYoQ%}|?Uu5y?Zt88Qp`5qc!v-lP+go|O z7r1!wNa;k5!p`556c>)t|1)$ZMj%`0iK>_gRK-Q3Eh!oWA;FLey-?^UM5cg$(qx4K z(@oyds^al8)X#n(H=XMtVDUAh=MeRj?hN!t4)t5@pgBJ{2nFoD$Qx28yQ9WSmUI6v zFVj26}x(v?K`0^6<37w|6XN-D12a?0aD}>HxJk->r6swF9 zCO%}iQejR}Y@Bvec6MQNTJra!CHXi{{nZ@_;h(joryT6h&l>8^OdD=bis>rx^^OcS zFl2VfqyI5@*;P&bHFnKjZ$J0a<8kID25HXDqKcSkVUj@b_RG9Evz1?(|J3@|7dMut3Iyc< diff --git a/implementation/03/public/index.php b/implementation/03/public/index.php index 970d132..d93da3a 100644 --- a/implementation/03/public/index.php +++ b/implementation/03/public/index.php @@ -1,3 +1,5 @@ -pushHandler( + new CallbackHandler( + function (Throwable $e) use ($environment) { + if ($environment !== 'dev') { + http_response_code(500); + echo 'Whoops'; + } + error_log(<<getMessage()} + {$e->getTraceAsString()} + TXT + ); + } + ) +); if ($environment === 'dev') { - $whoops->pushHandler(new PrettyPageHandler()); -} else { - $whoops->pushHandler(function (\Throwable $t) { - error_log('ERROR: ' . $t->getMessage(), $t->getCode()); - echo 'Oooopsie'; - }); + $whoops->pushHandler(new PrettyPageHandler); } - $whoops->register(); -echo 'Hello World!'; +throw new \Exception('Hello world'); \ No newline at end of file diff --git a/implementation/12/composer.lock b/implementation/12/composer.lock deleted file mode 100644 index c3de49e..0000000 --- a/implementation/12/composer.lock +++ /dev/null @@ -1,1516 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "ea5b586e05e6b1d75bded814662047b4", - "packages": [ - { - "name": "filp/whoops", - "version": "2.14.5", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.5" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2022-01-07T12:00:00+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.11.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/d1bc565b23c2040fafde398a8a5db083c47928c0", - "reference": "d1bc565b23c2040fafde398a8a5db083c47928c0", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2022-05-17T10:57:52+00:00" - }, - { - "name": "laravel/serializable-closure", - "version": "v1.2.0", - "source": { - "type": "git", - "url": "https://github.com/laravel/serializable-closure.git", - "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", - "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", - "shasum": "" - }, - "require": { - "php": "^7.3|^8.0" - }, - "require-dev": { - "pestphp/pest": "^1.18", - "phpstan/phpstan": "^0.12.98", - "symfony/var-dumper": "^5.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Laravel\\SerializableClosure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - }, - { - "name": "Nuno Maduro", - "email": "nuno@laravel.com" - } - ], - "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", - "keywords": [ - "closure", - "laravel", - "serializable" - ], - "support": { - "issues": "https://github.com/laravel/serializable-closure/issues", - "source": "https://github.com/laravel/serializable-closure" - }, - "time": "2022-05-16T17:09:47+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.14.1", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "support": { - "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" - }, - "time": "2022-01-21T06:08:36+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2021-12-13T09:22:56+00:00" - }, - { - "name": "php-di/php-di", - "version": "6.4.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/ae0f1b3b03d8b29dff81747063cbfd6276246cc4", - "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4", - "shasum": "" - }, - "require": { - "laravel/serializable-closure": "^1.0", - "php": ">=7.4.0", - "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "doctrine/annotations": "~1.10", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.11.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^9.5" - }, - "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.4.0" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2022-04-09T16:46:38+00:00" - }, - { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", - "keywords": [ - "phpdoc", - "reflection" - ], - "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" - }, - "time": "2020-10-12T12:39:22+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/http-server-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" - }, - "time": "2018-10-30T16:46:14+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2022-02-04T12:51:07+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "reference": "66c7adc9dfa38b6b5838a9fb728b68a7d8348051", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.1 || ^8.0", - "phpstan/phpstan": ">=0.11.6" - }, - "require-dev": { - "composer/composer": "^1.8", - "phing/phing": "^2.16.3", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.1.0" - }, - "time": "2020-12-13T13:06:13+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "1.5.1", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "981cc368a216c988e862a75e526b6076987d1b50" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", - "reference": "981cc368a216c988e862a75e526b6076987d1b50", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.5", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5", - "symfony/process": "^5.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" - }, - "time": "2022-05-05T11:32:40+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.6.8", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", - "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.6.8" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-05-10T06:54:21+00:00" - }, - { - "name": "phpstan/phpstan-strict-rules", - "version": "1.2.3", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", - "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.6.3" - }, - "require-dev": { - "nikic/php-parser": "^4.13.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" - }, - "type": "phpstan-extension", - "extra": { - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Extra strict and opinionated rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" - }, - "time": "2022-05-04T15:20:40+00:00" - }, - { - "name": "rector/rector", - "version": "0.12.23", - "source": { - "type": "git", - "url": "https://github.com/rectorphp/rector.git", - "reference": "690b31768b322db886b35845f8452025eba2cacb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/690b31768b322db886b35845f8452025eba2cacb", - "reference": "690b31768b322db886b35845f8452025eba2cacb", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0", - "phpstan/phpstan": "^1.6" - }, - "conflict": { - "phpstan/phpdoc-parser": "<1.2", - "rector/rector-cakephp": "*", - "rector/rector-doctrine": "*", - "rector/rector-laravel": "*", - "rector/rector-nette": "*", - "rector/rector-phpoffice": "*", - "rector/rector-phpunit": "*", - "rector/rector-prefixed": "*", - "rector/rector-symfony": "*" - }, - "bin": [ - "bin/rector" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "0.12-dev" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Instant Upgrade and Automated Refactoring of any PHP code", - "support": { - "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/0.12.23" - }, - "funding": [ - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2022-05-01T15:50:16+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "7.2.0", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", - "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.2 || ^8.0", - "phpstan/phpdoc-parser": "^1.5.1", - "squizlabs/php_codesniffer": "^3.6.2" - }, - "require-dev": { - "phing/phing": "2.17.3", - "php-parallel-lint/php-parallel-lint": "1.3.2", - "phpstan/phpstan": "1.4.10|1.6.7", - "phpstan/phpstan-deprecation-rules": "1.0.0", - "phpstan/phpstan-phpunit": "1.0.0|1.1.1", - "phpstan/phpstan-strict-rules": "1.2.3", - "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "7.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2022-05-06T10:58:42+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-30T18:21:41+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.8", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa61dfb4bd3068df2492013dc65f3190e9f550c0", - "reference": "fa61dfb4bd3068df2492013dc65f3190e9f550c0", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.8" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-04-26T13:22:23+00:00" - }, - { - "name": "symplify/easy-coding-standard", - "version": "10.2.6", - "source": { - "type": "git", - "url": "https://github.com/symplify/easy-coding-standard.git", - "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", - "reference": "8875d8cd438756c9719fcdcc3b7d0c1d06515dd5", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "conflict": { - "friendsofphp/php-cs-fixer": "<3.0", - "squizlabs/php_codesniffer": "<3.6" - }, - "bin": [ - "bin/ecs" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "9.5-dev" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Prefixed scoped version of ECS package", - "support": { - "source": "https://github.com/symplify/easy-coding-standard/tree/10.2.6" - }, - "funding": [ - { - "url": "https://www.paypal.me/rectorphp", - "type": "custom" - }, - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2022-05-17T07:11:50+00:00" - }, - { - "name": "thecodingmachine/phpstan-strict-rules", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/phpstan-strict-rules.git", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/phpstan-strict-rules/zipball/2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "reference": "2ba8fa8b328c45f3b149c05def5bf96793c594b6", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "phpstan/phpstan": "^1.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^7.1" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "phpstan": { - "includes": [ - "phpstan-strict-rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TheCodingMachine\\PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Négrier", - "email": "d.negrier@thecodingmachine.com" - } - ], - "description": "A set of additional rules for PHPStan based on best practices followed at TheCodingMachine", - "support": { - "issues": "https://github.com/thecodingmachine/phpstan-strict-rules/issues", - "source": "https://github.com/thecodingmachine/phpstan-strict-rules/tree/v1.0.0" - }, - "time": "2021-11-08T09:10:49+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.3.0" -}